Spark-NF / hls-downloader

Download HLS streams in NodeJS
Apache License 2.0
72 stars 31 forks source link

Doesn't work when files in the m3u8 are referenced relatively #4

Closed Connum closed 4 years ago

Connum commented 4 years ago

Looks very promising, but it failed for the first HLS stream that I tried. Quite often, files in the m3u8 (and any m3u8 files nested inside the main playlist) are referenced by a relative instead of an absolute URL:

example: The content of a playlist https:/some.example.com/playlist.m3u8 may simply be

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=3851727,CODECS="avc1.77.31,mp4a.40.2",RESOLUTION=1280x720
chunklist.m3u8

So the script would need to require the file at https:/some.example.com/chunklist.m3u8. That file might include something along the lines of

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10,
media_0.ts
#EXTINF:10,
media_1.ts
#EXTINF:10,
media_2.ts
...
#EXT-X-ENDLIST

The .ts files would then need to be requested from https:/some.example.com/media_0.ts.

However, currently the script fails with relative paths, resulting in an error message like this:

(node:1252) UnhandledPromiseRejectionWarning: Error: Invalid URI "chunklist.m3u8"
    at Request.init (C:\***\node-hls-downloader\node_modules\request\request.js:273:31)
    at new Request (C:\***\node-hls-downloader\node_modules\request\request.js:127:8)
    at request (C:\***\node-hls-downloader\node_modules\request\index.js:53:10)
    at Function.get (C:\***\node-hls-downloader\node_modules\request\index.js:61:12)
    at Promise (C:\***\node-hls-downloader\dist\index.js:18:17)
    at new Promise (<anonymous>)
    at get (C:\***\node-hls-downloader\dist\index.js:17:12)
    at ChunksDownloader.loadPlaylist (C:\***\node-hls-downloader\dist\index.js:93:32)
    at ChunksDownloader.refreshPlayList (C:\***\node-hls-downloader\dist\index.js:55:37)
    at queue.add (C:\***\node-hls-downloader\dist\index.js:51:39)
(node:1252) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:1252) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

To fix this, the script would have to check if it encounters a relative URL (anything without a protocol should to...) and in this case prepend the full URL of the previous file (not only the first URL, so that if the first playlist had listed a file in chunks/chunklist.m3u, the file should be requested at https://some.example.com/chunks/chunklist.m3u8 accordingly, and if that list had the files listed as tsfiles/001.ts, it should be handled correctly as https://some.example.com/chunks/tsfiles/001.ts, etc.)

Spark-NF commented 4 years ago

Indeed, currently all values are passed directly from the M3U8 files. I was using this program on files with absolute paths, so I didn't notice, sorry about that.

Using Node's URL class should be more than enough to fix the issue, as long as a proper "base" URL is provided. It should handle protocol detection and everything properly.

I just pushed a fix (b3c89c16d72b5632d13a092b02d1a47de3dd91ad), and I'll add more tests and make a new release (1.1.0) this week-end. 👍

Connum commented 4 years ago

Thanks for the quick reaction, I'll give it a try soon!