diff --git a/src/parseAttributes.js b/src/parseAttributes.js index 70dd20aa..dd0d26d4 100644 --- a/src/parseAttributes.js +++ b/src/parseAttributes.js @@ -1,3 +1,4 @@ +import { parseDivisionValue } from './utils/string'; import { from } from './utils/list'; import { parseDuration, parseDate } from './utils/time'; @@ -131,6 +132,18 @@ export const parsers = { return parseInt(value, 10); }, + /** + * Specifies the frame rate of the representation + * + * @param {string} value + * value of attribute as a string + * @return {number} + * The parsed frame rate + */ + frameRate(value) { + return parseDivisionValue(value); + }, + /** * Specifies the number of the first Media Segment in this Representation in the Period * diff --git a/src/toM3u8.js b/src/toM3u8.js index a643a8d9..1bc8f4e0 100644 --- a/src/toM3u8.js +++ b/src/toM3u8.js @@ -296,6 +296,10 @@ export const formatVideoPlaylist = ({ segments }; + if (attributes.frameRate) { + playlist.attributes['FRAME-RATE'] = attributes.frameRate; + } + if (attributes.contentProtection) { playlist.contentProtection = attributes.contentProtection; } diff --git a/src/utils/string.js b/src/utils/string.js new file mode 100644 index 00000000..4666560e --- /dev/null +++ b/src/utils/string.js @@ -0,0 +1,10 @@ +/** + * Converts the provided string that may contain a division operation to a number. + * + * @param {string} value - the provided string value + * + * @return {number} the parsed string value + */ +export const parseDivisionValue = (value) => { + return parseFloat(value.split('/').reduce((prev, current) => prev / current)); +}; diff --git a/test/manifests/608-captions.js b/test/manifests/608-captions.js index 86c0f4dd..ae12c995 100644 --- a/test/manifests/608-captions.js +++ b/test/manifests/608-captions.js @@ -36,6 +36,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/708-captions.js b/test/manifests/708-captions.js index 1528a3c6..4b3e682a 100644 --- a/test/manifests/708-captions.js +++ b/test/manifests/708-captions.js @@ -37,6 +37,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/location.js b/test/manifests/location.js index 896e10ed..c7fe2469 100644 --- a/test/manifests/location.js +++ b/test/manifests/location.js @@ -22,6 +22,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/locations.js b/test/manifests/locations.js index 31536f64..6b2ac590 100644 --- a/test/manifests/locations.js +++ b/test/manifests/locations.js @@ -23,6 +23,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/maat_vtt_segmentTemplate.js b/test/manifests/maat_vtt_segmentTemplate.js index 27d62a92..b7bbc6e9 100644 --- a/test/manifests/maat_vtt_segmentTemplate.js +++ b/test/manifests/maat_vtt_segmentTemplate.js @@ -406,6 +406,7 @@ export const parsedManifest = { 'NAME': '482', 'AUDIO': 'audio', 'SUBTITLES': 'subs', + 'FRAME-RATE': 23.976, 'RESOLUTION': { width: 482, height: 270 @@ -487,6 +488,7 @@ export const parsedManifest = { 'NAME': '720', 'AUDIO': 'audio', 'SUBTITLES': 'subs', + 'FRAME-RATE': 23.976, 'RESOLUTION': { width: 720, height: 404 diff --git a/test/manifests/multiperiod-dynamic.js b/test/manifests/multiperiod-dynamic.js index c83a669a..2d98b4ee 100644 --- a/test/manifests/multiperiod-dynamic.js +++ b/test/manifests/multiperiod-dynamic.js @@ -613,16 +613,17 @@ export const parsedManifest = { playlists: [ { attributes: { - 'NAME': 'default_video2000_0_1280x720', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 2008000, + 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 29.97, + 'NAME': 'default_video2000_0_1280x720', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 1280, - height: 720 + height: 720, + width: 1280 }, - 'CODECS': 'avc1.4d001f', - 'BANDWIDTH': 2008000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: false, @@ -905,16 +906,17 @@ export const parsedManifest = { }, { attributes: { - 'NAME': 'default_video1200_1_960x540', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 1195000, + 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 29.97, + 'NAME': 'default_video1200_1_960x540', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 960, - height: 540 + height: 540, + width: 960 }, - 'CODECS': 'avc1.4d001f', - 'BANDWIDTH': 1195000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: false, @@ -1197,16 +1199,17 @@ export const parsedManifest = { }, { attributes: { - 'NAME': 'default_video900_1_640x360', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 884000, + 'CODECS': 'avc1.4d001e', + 'FRAME-RATE': 29.97, + 'NAME': 'default_video900_1_640x360', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 640, - height: 360 + height: 360, + width: 640 }, - 'CODECS': 'avc1.4d001e', - 'BANDWIDTH': 884000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: false, diff --git a/test/manifests/multiperiod-segment-list.js b/test/manifests/multiperiod-segment-list.js index c37ab53b..aaf8be6e 100644 --- a/test/manifests/multiperiod-segment-list.js +++ b/test/manifests/multiperiod-segment-list.js @@ -15,6 +15,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/multiperiod-segment-template.js b/test/manifests/multiperiod-segment-template.js index 46132098..6cc21b98 100644 --- a/test/manifests/multiperiod-segment-template.js +++ b/test/manifests/multiperiod-segment-template.js @@ -130,16 +130,17 @@ export const parsedManifest = { playlists: [ { attributes: { - 'NAME': '1', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 100000, + 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 24, + 'NAME': '1', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 480, - height: 200 + height: 200, + width: 480 }, - 'CODECS': 'avc1.4d001f', - 'BANDWIDTH': 100000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, diff --git a/test/manifests/multiperiod-startnumber-removed-periods.js b/test/manifests/multiperiod-startnumber-removed-periods.js index c5be00ed..e81f653f 100644 --- a/test/manifests/multiperiod-startnumber-removed-periods.js +++ b/test/manifests/multiperiod-startnumber-removed-periods.js @@ -91,6 +91,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 2942295, 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 30, 'NAME': 'D', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -155,6 +156,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 4267536, 'CODECS': 'avc1.640020', + 'FRAME-RATE': 60, 'NAME': 'E', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -219,6 +221,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 5256859, 'CODECS': 'avc1.640020', + 'FRAME-RATE': 60, 'NAME': 'F', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -283,6 +286,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 240781, 'CODECS': 'avc1.4d000d', + 'FRAME-RATE': 30, 'NAME': 'A', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -347,6 +351,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 494354, 'CODECS': 'avc1.4d001e', + 'FRAME-RATE': 30, 'NAME': 'B', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -411,6 +416,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 1277155, 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 30, 'NAME': 'C', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/multiperiod-startnumber.js b/test/manifests/multiperiod-startnumber.js index 22349e37..a7adeacb 100644 --- a/test/manifests/multiperiod-startnumber.js +++ b/test/manifests/multiperiod-startnumber.js @@ -180,6 +180,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 2942295, 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 30, 'NAME': 'D', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -333,6 +334,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 4267536, 'CODECS': 'avc1.640020', + 'FRAME-RATE': 60, 'NAME': 'E', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -486,6 +488,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 5256859, 'CODECS': 'avc1.640020', + 'FRAME-RATE': 60, 'NAME': 'F', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -639,6 +642,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 240781, 'CODECS': 'avc1.4d000d', + 'FRAME-RATE': 30, 'NAME': 'A', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -792,6 +796,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 494354, 'CODECS': 'avc1.4d001e', + 'FRAME-RATE': 30, 'NAME': 'B', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -945,6 +950,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 1277155, 'CODECS': 'avc1.4d001e', + 'FRAME-RATE': 30, 'NAME': 'C', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/multiperiod.js b/test/manifests/multiperiod.js index e9cdaa17..c0550ad4 100644 --- a/test/manifests/multiperiod.js +++ b/test/manifests/multiperiod.js @@ -613,16 +613,17 @@ export const parsedManifest = { playlists: [ { attributes: { - 'NAME': 'default_video2000_0_1280x720', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 2008000, + 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 29.97, + 'NAME': 'default_video2000_0_1280x720', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 1280, - height: 720 + height: 720, + width: 1280 }, - 'CODECS': 'avc1.4d001f', - 'BANDWIDTH': 2008000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, @@ -905,16 +906,17 @@ export const parsedManifest = { }, { attributes: { - 'NAME': 'default_video1200_1_960x540', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 1195000, + 'CODECS': 'avc1.4d001f', + 'FRAME-RATE': 29.97, + 'NAME': 'default_video1200_1_960x540', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 960, - height: 540 + height: 540, + width: 960 }, - 'CODECS': 'avc1.4d001f', - 'BANDWIDTH': 1195000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, @@ -1197,16 +1199,17 @@ export const parsedManifest = { }, { attributes: { - 'NAME': 'default_video900_1_640x360', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 884000, + 'CODECS': 'avc1.4d001e', + 'FRAME-RATE': 29.97, + 'NAME': 'default_video900_1_640x360', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 640, - height: 360 + height: 360, + width: 640 }, - 'CODECS': 'avc1.4d001e', - 'BANDWIDTH': 884000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, diff --git a/test/manifests/segmentBase.js b/test/manifests/segmentBase.js index 5a43f9ed..f45ab909 100644 --- a/test/manifests/segmentBase.js +++ b/test/manifests/segmentBase.js @@ -15,6 +15,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/segmentList.js b/test/manifests/segmentList.js index bbc0fa95..9b7ddce8 100644 --- a/test/manifests/segmentList.js +++ b/test/manifests/segmentList.js @@ -15,6 +15,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 449000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '482', 'PROGRAM-ID': 1, 'RESOLUTION': { @@ -112,6 +113,7 @@ export const parsedManifest = { 'AUDIO': 'audio', 'BANDWIDTH': 3971000, 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, 'NAME': '720', 'PROGRAM-ID': 1, 'RESOLUTION': { diff --git a/test/manifests/vtt_codecs.js b/test/manifests/vtt_codecs.js index abc72a3a..46dcbae9 100644 --- a/test/manifests/vtt_codecs.js +++ b/test/manifests/vtt_codecs.js @@ -425,16 +425,17 @@ export const parsedManifest = { playlists: [ { attributes: { - 'NAME': '482', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 449000, + 'CODECS': 'avc1.420015', + 'FRAME-RATE': 23.976, + 'NAME': '482', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 482, - height: 270 + height: 270, + width: 482 }, - 'CODECS': 'avc1.420015', - 'BANDWIDTH': 449000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, @@ -509,16 +510,17 @@ export const parsedManifest = { }, { attributes: { - 'NAME': '720', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 3971000, + 'CODECS': 'avc1.64001e', + 'FRAME-RATE': 23.976, + 'NAME': '720', + 'PROGRAM-ID': 1, 'RESOLUTION': { - width: 720, - height: 404 + height: 404, + width: 720 }, - 'CODECS': 'avc1.64001e', - 'BANDWIDTH': 3971000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, diff --git a/test/manifests/webmsegments.js b/test/manifests/webmsegments.js index cbf5b4a6..daae0108 100644 --- a/test/manifests/webmsegments.js +++ b/test/manifests/webmsegments.js @@ -93,16 +93,17 @@ export const parsedManifest = { playlists: [ { attributes: { - 'NAME': '1', 'AUDIO': 'audio', - 'SUBTITLES': 'subs', + 'BANDWIDTH': 100000, + 'CODECS': 'av1', + 'FRAME-RATE': 24, + 'NAME': '1', + 'PROGRAM-ID': 1, 'RESOLUTION': { width: 480, height: 200 }, - 'CODECS': 'av1', - 'BANDWIDTH': 100000, - 'PROGRAM-ID': 1 + 'SUBTITLES': 'subs' }, uri: '', endList: true, diff --git a/test/toM3u8.test.js b/test/toM3u8.test.js index cefbcc04..664c07bc 100644 --- a/test/toM3u8.test.js +++ b/test/toM3u8.test.js @@ -42,6 +42,7 @@ QUnit.test('playlists', function(assert) { codecs: 'foo;bar', duration: 0, bandwidth: 10000, + frameRate: 30, periodStart: 0, mimeType: 'video/mp4', type: 'static' @@ -187,6 +188,7 @@ QUnit.test('playlists', function(assert) { BANDWIDTH: 10000, CODECS: 'foo;bar', NAME: '1', + ['FRAME-RATE']: 30, ['PROGRAM-ID']: 1, RESOLUTION: { height: 600,