From f31bf8721a8ad6efc4a90bd683b688214e209bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Birm=C3=A9?= Date: Mon, 22 Apr 2024 15:35:41 +0200 Subject: [PATCH] fix(#64): handle hls with init segments --- package-lock.json | 14 ++-- package.json | 2 +- src/manifests/handlers/hls/media.test.ts | 81 +++++++++++++++++++ src/manifests/utils/hlsManifestUtils.ts | 5 ++ src/shared/types.ts | 4 +- src/testvectors/hls/hls1_cmaf/manifest.m3u8 | 6 ++ src/testvectors/hls/hls1_cmaf/manifest_1.m3u8 | 16 ++++ 7 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 src/testvectors/hls/hls1_cmaf/manifest.m3u8 create mode 100644 src/testvectors/hls/hls1_cmaf/manifest_1.m3u8 diff --git a/package-lock.json b/package-lock.json index 5cfd8cf..8d7a8cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-ssm": "^3.306.0", - "@eyevinn/m3u8": ">=0.1.2", + "@eyevinn/m3u8": ">=0.5.6", "@fastify/aws-lambda": "^3.2.0", "aws-lambda": "^1.0.7", "dotenv": "^8.2.0", @@ -2455,9 +2455,9 @@ } }, "node_modules/@eyevinn/m3u8": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.5.2.tgz", - "integrity": "sha512-xIcBKo36ZhMG3JEM4r4+T/Bpz1QRG5wlbizo7d1onop6x76NUi+ACkXXxDlna7j+ELlljVhbkGhu0zTfCpJbHQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.5.6.tgz", + "integrity": "sha512-aYWN9Rzofs3MCuoGrJww6vExnKg8bLHN2jAmzjK8t/akwT3bzwR3i2kqH895ZysR87mikgOzF3IaBp4OYYfwWg==", "dependencies": { "chunked-stream": "~0.0.2" } @@ -14925,9 +14925,9 @@ } }, "@eyevinn/m3u8": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.5.2.tgz", - "integrity": "sha512-xIcBKo36ZhMG3JEM4r4+T/Bpz1QRG5wlbizo7d1onop6x76NUi+ACkXXxDlna7j+ELlljVhbkGhu0zTfCpJbHQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.5.6.tgz", + "integrity": "sha512-aYWN9Rzofs3MCuoGrJww6vExnKg8bLHN2jAmzjK8t/akwT3bzwR3i2kqH895ZysR87mikgOzF3IaBp4OYYfwWg==", "requires": { "chunked-stream": "~0.0.2" } diff --git a/package.json b/package.json index e371863..1b9669e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@aws-sdk/client-ssm": "^3.306.0", - "@eyevinn/m3u8": ">=0.1.2", + "@eyevinn/m3u8": ">=0.5.6", "@fastify/aws-lambda": "^3.2.0", "aws-lambda": "^1.0.7", "dotenv": "^8.2.0", diff --git a/src/manifests/handlers/hls/media.test.ts b/src/manifests/handlers/hls/media.test.ts index e78a4af..4888d42 100644 --- a/src/manifests/handlers/hls/media.test.ts +++ b/src/manifests/handlers/hls/media.test.ts @@ -375,6 +375,87 @@ https://mock.mock.com/stream/hls/manifest_1_00001.ts expect(response.body).toEqual(expected.body); }); + it('should handle init segments for HLS cmaf media', async () => { + // Arrange + const getMedia = () => { + return new Promise((resolve) => { + const readStream: ReadStream = createReadStream( + path.join( + __dirname, + `../../../testvectors/hls/hls1_cmaf/manifest_1.m3u8` + ) + ); + resolve(readStream); + }); + }; + nock(mockBaseURL).persist().get('/manifest_1.m3u8').reply(200, getMedia, { + 'Content-Type': 'application/vnd.apple.mpegurl;charset=UTF-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type, Origin' + }); + + const queryParams = { + url: mockMediaURL, + statusCode: '[{i:2,ms:3000}]' + }; + const event: ALBEvent = { + requestContext: { + elb: { + targetGroupArn: '' + } + }, + path: '/stream/hls/manifest.m3u8', + httpMethod: 'GET', + headers: { + accept: 'application/vnd.apple.mpegurl;charset=UTF-8', + 'accept-language': 'en-US,en;q=0.8', + 'content-type': 'text/plain', + host: 'lambda-846800462-us-east-2.elb.amazonaws.com', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)', + 'x-amzn-trace-id': 'Root=1-5bdb40ca-556d8b0c50dc66f0511bf520', + 'x-forwarded-for': '72.21.198.xx', + 'x-forwarded-port': '443', + 'x-forwarded-proto': 'https' + }, + isBase64Encoded: false, + queryStringParameters: queryParams, + body: '' + }; + + // Act + const response = await hlsMediaHandler(event); + + // Assert + const expected: ALBResult = { + statusCode: 200, + headers: { + 'Access-Control-Allow-Headers': 'Content-Type, Origin', + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/vnd.apple.mpegurl' + }, + body: `#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:4 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="https://mock.mock.com/stream/hls/subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000.m4s" +#EXT-X-PROGRAM-DATE-TIME:1970-01-01T00:00:00Z +#EXTINF:3.8400, no desc +https://mock.mock.com/stream/hls/subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000-1.m4s +#EXTINF:3.8400, no desc +https://mock.mock.com/stream/hls/subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000-2.m4s +#EXTINF:3.8400, no desc +https://mock.mock.com/stream/hls/subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000-3.m4s +#EXT-X-ENDLIST +` + }; + expect(response.statusCode).toEqual(expected.statusCode); + expect(response.headers).toEqual(expected.headers); + expect(response.body).toEqual(expected.body); + }); + //it('should return code 500 on Other Errors, eg M3U8 parser error', async () => {}); }); }); diff --git a/src/manifests/utils/hlsManifestUtils.ts b/src/manifests/utils/hlsManifestUtils.ts index d46083f..4a58cfa 100644 --- a/src/manifests/utils/hlsManifestUtils.ts +++ b/src/manifests/utils/hlsManifestUtils.ts @@ -156,6 +156,11 @@ export default function (): HLSManifestTools { if (!sourceSegURL.match(/^http/)) { sourceSegURL = `${sourceBaseURL}/${item.get('uri')}`; } + let sourceMapURL: string | undefined = item.get('map-uri'); + if (sourceMapURL && !sourceMapURL.match(/^http/)) { + sourceMapURL = `${sourceBaseURL}/${sourceMapURL}`; + item.set('map-uri', sourceMapURL); + } if (!corruption) { item.set('uri', sourceSegURL); diff --git a/src/shared/types.ts b/src/shared/types.ts index 4471ed4..430ee3b 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -17,8 +17,8 @@ export type TargetLevel = number; /* eslint-disable */ export type M3UItem = { - get: (key: 'uri' | 'type') => string | any; - set: (key: 'uri', value: string) => void; + get: (key: 'uri' | 'type' | 'map-uri') => string | any; + set: (key: 'uri' | 'map-uri', value: string) => void; }; export type M3U = { diff --git a/src/testvectors/hls/hls1_cmaf/manifest.m3u8 b/src/testvectors/hls/hls1_cmaf/manifest.m3u8 new file mode 100644 index 0000000..9de047f --- /dev/null +++ b/src/testvectors/hls/hls1_cmaf/manifest.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-STREAM-INF:BANDWIDTH=4255267,AVERAGE-BANDWIDTH=4255267,CODECS="avc1.4d4032,mp4a.40.2",RESOLUTION=2560x1440,FRAME-RATE=25.000,AUDIO="audio",SUBTITLES="subs" +manifest_1.m3u8 + diff --git a/src/testvectors/hls/hls1_cmaf/manifest_1.m3u8 b/src/testvectors/hls/hls1_cmaf/manifest_1.m3u8 new file mode 100644 index 0000000..a7d6751 --- /dev/null +++ b/src/testvectors/hls/hls1_cmaf/manifest_1.m3u8 @@ -0,0 +1,16 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:4 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000.m4s" +#EXT-X-PROGRAM-DATE-TIME:1970-01-01T00:00:00Z +#EXTINF:3.84, no desc +subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000-1.m4s +#EXTINF:3.84, no desc +subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000-2.m4s +#EXTINF:3.84, no desc +subdir/142d4370-56c9-11ee-a816-2da19aea6ed1_20571919-video=6500000-3.m4s +#EXT-X-ENDLIST