Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom tag mapping #73

Merged
merged 5 commits into from Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Expand Up @@ -312,6 +312,38 @@ parser.push(manifest);
parser.end();
parser.manifest.segments[0].custom.vodTiming // #VOD-TIMING:1511816599485
```

Custom parsers may also map data an existing tag type.
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
```js
const manifest = [
'#EXTM3U',
'#VOD-TIMING:wallclock=1511816599485',
'#EXTINF:8.0,',
'ex1.ts',
''
].join('\n');

const parser = new m3u8Parser.Parser();
parser.addTagMapper({
expression: /#VOD-TIMING/,
map(line) {
const regex = /#VOD-TIMING:(?:wallclock=([^,\n]+))/g;
const match = regex.exec(line);

if (match) {
const ISOdate = new Date(Number(match[1])).toISOString();
return `#EXT-X-PROGRAM-DATE-TIME:${ISOdate}`;
}

return line;
}
});

parser.push(manifest);
parser.end();
parser.manifest.segments[0].dateTimeObject // #VOD-TIMING:1511816599485
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
```

## Including the Parser

To include m3u8-parser on your website or web application, use any of the following methods.
Expand Down
25 changes: 25 additions & 0 deletions src/parse-stream.js
Expand Up @@ -75,6 +75,7 @@ export default class ParseStream extends Stream {
constructor() {
super();
this.customParsers = [];
this.tagMappers = [];
}

/**
Expand Down Expand Up @@ -103,6 +104,11 @@ export default class ParseStream extends Stream {
return;
}

// map tag
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < this.tagMappers.length; i++) {
line = this.tagMappers[i].call(this, line);
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
}

for (let i = 0; i < this.customParsers.length; i++) {
if (this.customParsers[i].call(this, line)) {
return;
Expand Down Expand Up @@ -463,4 +469,23 @@ export default class ParseStream extends Stream {
}
});
}

/**
* Add a custom header mapper
*
* @param {Object} options
* @param {RegExp} options.expression a regular expression to match the custom header
* @param {Function} options.map function to translate tag into a different tag
*/
addTagMapper({expression, map}) {
const mapFn = line => {
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
if (expression.test(line)) {
return map(line);
}

return line;
};

this.tagMappers.push(mapFn);
}
}
10 changes: 10 additions & 0 deletions src/parser.js
Expand Up @@ -393,4 +393,14 @@ export default class Parser extends Stream {
addParser(options) {
this.parseStream.addParser(options);
}
/**
* Add a custom header mapper
*
* @param {Object} options
* @param {RegExp} options.expression a regular expression to match the custom header
* @param {Function} options.map function to translate tag into a different tag
*/
addTagMapper(options) {
this.parseStream.addTagMapper(options);
}
}
60 changes: 60 additions & 0 deletions test/m3u8.test.js
Expand Up @@ -85,6 +85,7 @@ QUnit.module('ParseStream', {
this.lineStream.pipe(this.parseStream);
}
});

QUnit.test('parses custom tags', function(assert) {
const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
let element;
Expand All @@ -108,6 +109,65 @@ QUnit.test('parses custom tags', function(assert) {
);
});

QUnit.test('maps custom tags', function(assert) {
const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
let element;
let calls = 0;

this.parseStream.addTagMapper({
expression: /^#VOD-STARTTIMESTAMP/,
map(line) {
const match = /#VOD-STARTTIMESTAMP:(\d+)/g.exec(line);

calls++;

return `#INTERMEDIATE:${match[1]}`;
}
});

this.parseStream.addTagMapper({
expression: /^#INTERMEDIATE/,
map(line) {
const match = /#INTERMEDIATE:(.*)/g.exec(line)[1];
const ISOdate = new Date(Number(match)).toISOString();

calls++;
ldayananda marked this conversation as resolved.
Show resolved Hide resolved

return `#EXT-X-PROGRAM-DATE-TIME:${ISOdate}`;
}
});

this.parseStream.on('data', function(elem) {
element = elem;
});

this.lineStream.push(manifest);
assert.ok(element, 'element');
assert.strictEqual(calls, 2);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be 1 since the regex test in mapFn should fail for the intermediate tag mapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mapping goes #VOD-STARTTIMESTAMP -> #INTERMEDIATE -> #EXT-X-PROGRAM-DATE-TIME on the same line. Each line being parsed will be processed by all registered mappers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 not sure we should allow this sort of chained tag mapping since tag mappers could be added by different users. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can have it explicitly break on the first matching mapper however I think it's a valid use case if say the BC player adds some mappers and a BC user would like to transform that data further

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, that's a good point. Maybe a warning would work?

assert.strictEqual(element.type, 'tag');
assert.strictEqual(element.dateTimeString,
'2017-07-31T20:35:37.573Z');
});

QUnit.test('mapper ignores tags', function(assert) {
const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
let element;

this.parseStream.addTagMapper({
expression: /^#NO-MATCH/,
map(line) {
return '#MAPPED';
}
});

this.parseStream.on('data', function(elem) {
element = elem;
});

this.lineStream.push(manifest);
assert.strictEqual(element.text, 'VOD-STARTTIMESTAMP:1501533337573');
ldayananda marked this conversation as resolved.
Show resolved Hide resolved
});

QUnit.test('parses comment lines', function(assert) {
const manifest = '# a line that starts with a hash mark without "EXT" is a comment\n';
let element;
Expand Down