From c03376096df8a382aeffe20f596930e03ba468c9 Mon Sep 17 00:00:00 2001 From: Brandon Casey <2381475+brandonocasey@users.noreply.github.com> Date: Wed, 20 Jan 2021 10:12:38 -0500 Subject: [PATCH] feat: add support for #EXT-X-PART (#127) https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls#3282436 https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-4.4.4.9 --- src/parse-stream.js | 34 ++++++++ src/parser.js | 4 + test/fixtures/m3u8/llhls.json | 116 ++++++++++++++++++++++++++++ test/fixtures/m3u8/llhls.m3u8 | 4 +- test/fixtures/m3u8/llhlsDelta.json | 120 +++++++++++++++++++++++++++++ test/fixtures/m3u8/llhlsDelta.m3u8 | 4 +- 6 files changed, 278 insertions(+), 4 deletions(-) diff --git a/src/parse-stream.js b/src/parse-stream.js index 18f83e5..4374016 100644 --- a/src/parse-stream.js +++ b/src/parse-stream.js @@ -464,6 +464,40 @@ export default class ParseStream extends Stream { this.trigger('data', event); return; } + match = (/^#EXT-X-PART:(.*)$/).exec(newLine); + if (match && match[1]) { + event = { + type: 'tag', + tagType: 'part' + }; + event.attributes = parseAttributes(match[1]); + ['DURATION'].forEach(function(key) { + if (event.attributes.hasOwnProperty(key)) { + event.attributes[key] = parseFloat(event.attributes[key]); + } + }); + + ['INDEPENDENT', 'GAP'].forEach(function(key) { + if (event.attributes.hasOwnProperty(key)) { + event.attributes[key] = (/YES/).test(event.attributes[key]); + } + }); + + if (event.attributes.hasOwnProperty('BYTERANGE')) { + const [length, offset] = event.attributes.BYTERANGE.split('@'); + + event.byterange = {}; + if (length) { + event.byterange.length = parseInt(length, 10); + } + if (offset) { + event.byterange.offset = parseInt(offset, 10); + } + } + + this.trigger('data', event); + return; + } // unknown tag type this.trigger('data', { diff --git a/src/parser.js b/src/parser.js index 1c4e36a..f74e2d9 100644 --- a/src/parser.js +++ b/src/parser.js @@ -386,6 +386,10 @@ export default class Parser extends Stream { }, 'skip'() { this.manifest.skip = entry.attributes; + }, + 'part'() { + this.manifest.parts = this.manifest.parts || []; + this.manifest.parts.push(entry.attributes); } })[entry.tagType] || noop).call(self); }, diff --git a/test/fixtures/m3u8/llhls.json b/test/fixtures/m3u8/llhls.json index 3d82211..54b5846 100644 --- a/test/fixtures/m3u8/llhls.json +++ b/test/fixtures/m3u8/llhls.json @@ -5,6 +5,122 @@ "discontinuitySequence": 0, "discontinuityStarts": [], "mediaSequence": 266, + "parts": [ + { + "DURATION": 0.33334, + "URI": "filePart271.0.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.1.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.2.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.3.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart271.4.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.5.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.6.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.7.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart271.8.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.9.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.10.mp4" + }, + { + "BYTERANGE": "587500@522828", + "DURATION": 0.33334, + "URI": "filePart271.11.mp4" + }, + { + "DURATION": 0.33334, + "GAP": true, + "URI": "filePart272.a.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.b.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.c.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.d.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.e.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart272.f.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.g.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.h.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.i.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.j.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.k.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.l.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart273.0.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart273.1.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart273.2.mp4" + } + ], "segments": [ { "dateTimeObject": new Date("2019-02-14T02:13:36.106Z"), diff --git a/test/fixtures/m3u8/llhls.m3u8 b/test/fixtures/m3u8/llhls.m3u8 index d48f0a0..dc208ee 100644 --- a/test/fixtures/m3u8/llhls.m3u8 +++ b/test/fixtures/m3u8/llhls.m3u8 @@ -28,11 +28,11 @@ fileSequence270.mp4 #EXT-X-PART:DURATION=0.33334,URI="filePart271.8.mp4",INDEPENDENT=YES #EXT-X-PART:DURATION=0.33334,URI="filePart271.9.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart271.10.mp4" -#EXT-X-PART:DURATION=0.33334,URI="filePart271.11.mp4" +#EXT-X-PART:BYTERANGE=587500@522828,DURATION=0.33334,URI="filePart271.11.mp4" #EXTINF:4.00008, fileSequence271.mp4 #EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z -#EXT-X-PART:DURATION=0.33334,URI="filePart272.a.mp4" +#EXT-X-PART:GAP=YES,DURATION=0.33334,URI="filePart272.a.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart272.b.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart272.c.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart272.d.mp4" diff --git a/test/fixtures/m3u8/llhlsDelta.json b/test/fixtures/m3u8/llhlsDelta.json index e68928e..5bf8560 100644 --- a/test/fixtures/m3u8/llhlsDelta.json +++ b/test/fixtures/m3u8/llhlsDelta.json @@ -5,6 +5,126 @@ "discontinuitySequence": 0, "discontinuityStarts": [], "mediaSequence": 266, + "parts": [ + { + "DURATION": 0.33334, + "URI": "filePart271.0.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.1.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.2.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.3.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart271.4.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.5.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.6.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.7.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart271.8.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.9.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart271.10.mp4" + }, + { + "BYTERANGE": "587500@522828", + "DURATION": 0.33334, + "URI": "filePart271.11.mp4" + }, + { + "DURATION": 0.33334, + "GAP": true, + "URI": "filePart272.a.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.b.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.c.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.d.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.e.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart272.f.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.g.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.h.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.i.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.j.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.k.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart272.l.mp4" + }, + { + "DURATION": 0.33334, + "INDEPENDENT": true, + "URI": "filePart273.0.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart273.1.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart273.2.mp4" + }, + { + "DURATION": 0.33334, + "URI": "filePart273.3.mp4" + } + ], "segments": [ { "duration": 4.00008, diff --git a/test/fixtures/m3u8/llhlsDelta.m3u8 b/test/fixtures/m3u8/llhlsDelta.m3u8 index 01e2336..85e3719 100644 --- a/test/fixtures/m3u8/llhlsDelta.m3u8 +++ b/test/fixtures/m3u8/llhlsDelta.m3u8 @@ -21,11 +21,11 @@ fileSequence270.mp4 #EXT-X-PART:DURATION=0.33334,URI="filePart271.8.mp4",INDEPENDENT=YES #EXT-X-PART:DURATION=0.33334,URI="filePart271.9.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart271.10.mp4" -#EXT-X-PART:DURATION=0.33334,URI="filePart271.11.mp4" +#EXT-X-PART:BYTERANGE=587500@522828,DURATION=0.33334,URI="filePart271.11.mp4" #EXTINF:4.00008, fileSequence271.mp4 #EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z -#EXT-X-PART:DURATION=0.33334,URI="filePart272.a.mp4" +#EXT-X-PART:GAP=YES,DURATION=0.33334,URI="filePart272.a.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart272.b.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart272.c.mp4" #EXT-X-PART:DURATION=0.33334,URI="filePart272.d.mp4"