@@ -4,8 +4,6 @@ import { DateRange } from './date-range';
44import { Fragment , Part } from './fragment' ;
55import { LevelDetails } from './level-details' ;
66import { LevelKey } from './level-key' ;
7- import { KeySystemFormats } from '../utils/mediakeys-helper' ;
8-
97import { AttrList } from '../utils/attr-list' ;
108import { logger } from '../utils/logger' ;
119import type { CodecType } from '../utils/codecs' ;
@@ -20,9 +18,15 @@ import type { LevelAttributes, LevelParsed } from '../types/level';
2018
2119type M3U8ParserFragments = Array < Fragment | null > ;
2220
21+ type ParsedMultiVariantPlaylist = {
22+ levels : LevelParsed [ ] ;
23+ sessionData : Record < string , AttrList > | null ;
24+ sessionKeys : LevelKey [ ] | null ;
25+ } ;
26+
2327// https://regex101.com is your friend
2428const MASTER_PLAYLIST_REGEX =
25- / # E X T - X - S T R E A M - I N F : ( [ ^ \r \n ] * ) (?: [ \r \n ] (?: # [ ^ \r \n ] * ) ? ) * ( [ ^ \r \n ] + ) | # E X T - X - S E S S I O N - D A T A : ( [ ^ \r \n ] * ) [ \r \n ] + / g;
29+ / # E X T - X - S T R E A M - I N F : ( [ ^ \r \n ] * ) (?: [ \r \n ] (?: # [ ^ \r \n ] * ) ? ) * ( [ ^ \r \n ] + ) | # E X T - X - S E S S I O N - D A T A : ( [ ^ \r \n ] * ) [ \r \n ] + | # E X T - X - S E S S I O N - K E Y : ( [ ^ \n \r ] * ) [ \r \n ] + / g;
2630const MASTER_PLAYLIST_MEDIA_REGEX = / # E X T - X - M E D I A : ( .* ) / g;
2731
2832const LEVEL_PLAYLIST_REGEX_FAST = new RegExp (
@@ -84,10 +88,15 @@ export default class M3U8Parser {
8488 return URLToolkit . buildAbsoluteURL ( baseUrl , url , { alwaysNormalize : true } ) ;
8589 }
8690
87- static parseMasterPlaylist ( string : string , baseurl : string ) {
88- const levels : Array < LevelParsed > = [ ] ;
89- const levelsWithKnownCodecs : Array < LevelParsed > = [ ] ;
91+ static parseMasterPlaylist (
92+ string : string ,
93+ baseurl : string
94+ ) : ParsedMultiVariantPlaylist {
95+ const levels : LevelParsed [ ] = [ ] ;
96+ const levelsWithKnownCodecs : LevelParsed [ ] = [ ] ;
9097 const sessionData : Record < string , AttrList > = { } ;
98+ const sessionKeys : LevelKey [ ] = [ ] ;
99+
91100 let hasSessionData = false ;
92101 MASTER_PLAYLIST_REGEX . lastIndex = 0 ;
93102
@@ -132,6 +141,17 @@ export default class M3U8Parser {
132141 hasSessionData = true ;
133142 sessionData [ sessionAttrs [ 'DATA-ID' ] ] = sessionAttrs ;
134143 }
144+ } else if ( result [ 4 ] ) {
145+ // '#EXT-X-SESSION-KEY' is found
146+ const keyTag = result [ 4 ] ;
147+ const sessionKey = parseKey ( keyTag , baseurl ) ;
148+ if ( sessionKey . encrypted && sessionKey . isSupported ( ) ) {
149+ sessionKeys . push ( sessionKey ) ;
150+ } else {
151+ logger . warn (
152+ `[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "${ keyTag } "`
153+ ) ;
154+ }
135155 }
136156 }
137157 // Filter out levels with unknown codecs if it does not remove all levels
@@ -142,6 +162,7 @@ export default class M3U8Parser {
142162 return {
143163 levels : stripUnknownCodecLevels ? levelsWithKnownCodecs : levels ,
144164 sessionData : hasSessionData ? sessionData : null ,
165+ sessionKeys : sessionKeys . length ? sessionKeys : null ,
145166 } ;
146167 }
147168
@@ -378,64 +399,21 @@ export default class M3U8Parser {
378399 discontinuityCounter = parseInt ( value1 ) ;
379400 break ;
380401 case 'KEY' : {
381- // https://tools.ietf.org/html/rfc8216#section-4.3.2.4
382- const keyAttrs = new AttrList ( value1 ) ;
383- const decryptmethod = keyAttrs . enumeratedString ( 'METHOD' ) ;
384- const decrypturi = keyAttrs . URI ;
385- const decryptiv = keyAttrs . hexadecimalInteger ( 'IV' ) ;
386- const decryptkeyformatversions =
387- keyAttrs . enumeratedString ( 'KEYFORMATVERSIONS' ) ;
388- // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity".
389- const decryptkeyformat =
390- keyAttrs . enumeratedString ( 'KEYFORMAT' ) ?? 'identity' ;
391-
392- if (
393- ! decryptmethod ||
394- [
395- 'NONE' ,
396- 'AES-128' ,
397- 'ISO-23001-7' ,
398- 'SAMPLE-AES' ,
399- 'SAMPLE-AES-CENC' ,
400- 'SAMPLE-AES-CTR' ,
401- ] . indexOf ( decryptmethod ) === - 1
402- ) {
403- logger . warn ( `[Keys] Ignoring invalid EXT-X-KEY tag: "${ value1 } "` ) ;
404- } else {
405- if ( decrypturi && keyAttrs . IV && ! decryptiv ) {
406- logger . error ( `Invalid IV: ${ keyAttrs . IV } ` ) ;
402+ const levelKey = parseKey ( value1 , baseurl ) ;
403+ if ( levelKey . isSupported ( ) ) {
404+ if ( levelKey . method === 'NONE' ) {
405+ levelkeys = undefined ;
406+ break ;
407407 }
408- // If decrypturi is a URI with a scheme, then baseurl will be ignored
409- // No uri is allowed when METHOD is NONE
410- const resolvedUri = decrypturi
411- ? M3U8Parser . resolve ( decrypturi , baseurl )
412- : '' ;
413- const keyFormatVersions = (
414- decryptkeyformatversions ? decryptkeyformatversions : '1'
415- )
416- . split ( '/' )
417- . map ( Number )
418- . filter ( Number . isFinite ) ;
419-
420- if ( isKeyTagSupported ( decryptkeyformat , decryptmethod ) ) {
421- if ( decryptmethod === 'NONE' ) {
422- levelkeys = undefined ;
423- break ;
424- }
425- if ( ! levelkeys ) {
426- levelkeys = { } ;
427- }
428- if ( levelkeys [ decryptkeyformat ] ) {
429- levelkeys = Object . assign ( { } , levelkeys ) ;
430- }
431- levelkeys [ decryptkeyformat ] = new LevelKey (
432- decryptmethod ,
433- resolvedUri ,
434- decryptkeyformat ,
435- keyFormatVersions ,
436- decryptiv
437- ) ;
408+ if ( ! levelkeys ) {
409+ levelkeys = { } ;
438410 }
411+ if ( levelkeys [ levelKey . keyFormat ] ) {
412+ levelkeys = Object . assign ( { } , levelkeys ) ;
413+ }
414+ levelkeys [ levelKey . keyFormat ] = levelKey ;
415+ } else {
416+ logger . warn ( `[Keys] Ignoring invalid EXT-X-KEY tag: "${ value1 } "` ) ;
439417 }
440418 break ;
441419 }
@@ -600,25 +578,37 @@ export default class M3U8Parser {
600578 }
601579}
602580
603- function isKeyTagSupported (
604- decryptformat : string ,
605- decryptmethod : string
606- ) : boolean {
607- // If it's Segment encryption or No encryption, just select that key system
608- if ( 'AES-128' === decryptmethod || 'NONE' === decryptmethod ) {
609- return true ;
610- }
611- switch ( decryptformat ) {
612- case 'identity' :
613- // maintain support for clear SAMPLE-AES with MPEG-3 TS
614- return true ;
615- case KeySystemFormats . FAIRPLAY :
616- case KeySystemFormats . WIDEVINE :
617- case KeySystemFormats . PLAYREADY :
618- case KeySystemFormats . CLEARKEY :
619- return true ;
581+ function parseKey ( keyTag : string , baseurl : string ) : LevelKey {
582+ // https://tools.ietf.org/html/rfc8216#section-4.3.2.4
583+ const keyAttrs = new AttrList ( keyTag ) ;
584+ const decryptmethod = keyAttrs . enumeratedString ( 'METHOD' ) ?? '' ;
585+ const decrypturi = keyAttrs . URI ;
586+ const decryptiv = keyAttrs . hexadecimalInteger ( 'IV' ) ;
587+ const decryptkeyformatversions =
588+ keyAttrs . enumeratedString ( 'KEYFORMATVERSIONS' ) ;
589+ // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity".
590+ const decryptkeyformat = keyAttrs . enumeratedString ( 'KEYFORMAT' ) ?? 'identity' ;
591+
592+ if ( decrypturi && keyAttrs . IV && ! decryptiv ) {
593+ logger . error ( `Invalid IV: ${ keyAttrs . IV } ` ) ;
620594 }
621- return false ;
595+ // If decrypturi is a URI with a scheme, then baseurl will be ignored
596+ // No uri is allowed when METHOD is NONE
597+ const resolvedUri = decrypturi ? M3U8Parser . resolve ( decrypturi , baseurl ) : '' ;
598+ const keyFormatVersions = (
599+ decryptkeyformatversions ? decryptkeyformatversions : '1'
600+ )
601+ . split ( '/' )
602+ . map ( Number )
603+ . filter ( Number . isFinite ) ;
604+
605+ return new LevelKey (
606+ decryptmethod ,
607+ resolvedUri ,
608+ decryptkeyformat ,
609+ keyFormatVersions ,
610+ decryptiv
611+ ) ;
622612}
623613
624614function setCodecs ( codecs : Array < string > , level : LevelParsed ) {
0 commit comments