- proxy .m3u8 files, and the .ts files they internally reference
- to all proxied files:
- add permissive CORS response headers
- to .m3u8:
- modify contents such that URLs in the playlist will also pass through the proxy
- inject custom HTTP headers in all outbound proxied requests
- prefetch video segments (.ts files)
- use a hook function to conditionally redirect URLs in the playlist (before they're modified to pass through the proxy)
- any video player (on the LAN) can access the proxied video stream
- including Chromecast
- prefetch and caching of video segments ahead-of-time makes playback of the video stream very stable
- solves buffering problems
- the proxy can easily be configured to bypass many of the security measures used by video servers to restrict access:
- CORS response headers (to XHR requests)
- used by web browsers to enforce a security policy that limits which website(s) may access the content
- HTTP request headers
Origin
andReferer
are often inspected by the server- when these headers don't match the site hosting the content, a
403 Forbidden
response is returned (in lieu of the requested data)
- when these headers don't match the site hosting the content, a
- restricted access to encryption keys
- often times the encrypted video segments (.ts files) are readily available, but the encryption keys are well protected
- if the keys can be obtained from another source, then a hook function can be used to redirect only those URL requests
- often times the encrypted video segments (.ts files) are readily available, but the encryption keys are well protected
- CORS response headers (to XHR requests)
npm install --global "@warren-bank/hls-proxy"
hlsd [--help] [--version] [--tls] [--host <ip_address>] [--port <number>] [--req-headers <filepath>] [--origin <header>] [--referer <header>] [--useragent <header>] [--header <name=value>] [--req-options <filepath>] [--req-secure-honor-server-cipher-order] [--req-secure-ciphers <string>] [--req-secure-protocol <string>] [--req-secure-curve <string>] [--hooks <filepath>] [--prefetch] [--max-segments <number>] [--cache-key <number>] [-v <number>]
-
print help
hlsd --help
-
print version
hlsd --version
-
start HTTP proxy at default host:port
hlsd
-
start HTTP proxy at default host and specific port
hlsd --port "8080"
-
start HTTP proxy at specific host:port
hlsd --host "192.168.0.100" --port "8080"
-
start HTTPS proxy at default host:port
hlsd --tls
-
start HTTPS proxy at specific host:port
hlsd --tls --host "192.168.0.100" --port "8081"
-
start HTTPS proxy at default host:port and send specific HTTP headers
hlsd --tls --req-headers "/path/to/request/headers.json"
-
start HTTPS proxy at default host:port and enable prefetch of 10 video segments
hlsd --tls --prefetch --max-segments 10
- --tls is a flag to start HTTPS proxy, rather than HTTP
- --host must be an IP address of the server on the LAN (so Chromecast can proxy requests through it)
- ex:
192.168.0.100
- used to modify URLs in .m3u8 files
- when this option is not specified:
- the list of available network addresses is determined
- if there are none, 'localhost' is used silently
- if there is only a single address on the LAN, it is used silently
- if there are multiple addresses:
- they are listed
- a prompt asks the user to choose (the numeric index) of one
- ex:
- --port is the port number that the server listens on
- ex:
8080
- used to modify URLs in .m3u8 files
- when this option is not specified:
- HTTP proxy binds to:
80
- HTTPS proxy binds to:
443
- HTTP proxy binds to:
- ex:
- --req-headers is the filepath to a JSON data Object containing key:value pairs
- each key is the name of an HTTP header to send in in every outbound request
- --origin is the value of the corresponding HTTP request header
- --referer is the value of the corresponding HTTP request header
- --useragent is the value of the corresponding HTTP request header
- --header is a single name:value pair
- this option can be used multiple times to include several HTTP request headers
- the pair can be written:
- "name: value"
- "name=value"
- "name = value"
- --req-options is the filepath to a JSON data Object
- exposes the options Object passed to low-level network request APIs:
- advanced https request options:
- context of the secure request is obtained by passing the request options Object to:
tls.createSecureContext(options)
- configuration for the context of the secure request can be merged with the request options Object
- configuration keys of particular interest:
honorCipherOrder
- default value:
false
- default value:
ciphers
secureProtocol
- default value:
"TLS_method"
- default value:
ecdhCurve
- default value:
tls.DEFAULT_ECDH_CURVE
- the exact value depends on the version of node
- most commonly:
- older versions of node:
"prime256v1"
- newer versions of node:
"auto"
- older versions of node:
- default value:
- context of the secure request is obtained by passing the request options Object to:
- --req-secure-honor-server-cipher-order is a flag to set the following key in the request options Object to configure the context for secure https requests:
{honorCipherOrder: true}
- --req-secure-ciphers is the value to assign to the following key in the request options Object to configure the context for secure https requests:
{ciphers: value}
- --req-secure-protocol is the value to assign to the following key in the request options Object to configure the context for secure https requests:
{secureProtocol: value}
- --req-secure-curve is the value to assign to the following key in the request options Object to configure the context for secure https requests:
{ecdhCurve: value}
- --hooks is the filepath to a CommonJS module that exports a single JSON Object
- each key is the name of a hook function
- each value is the implementation of the corresponding Function
- hook function signatures:
"redirect": (url) => new_url
- conditionally redirect the URLs encountered in .m3u8 files before they are modified to pass through the proxy
- --prefetch is a flag to enable the prefetch and caching of video segments
- when .m3u8 files are downloaded and modified inflight, all of the URLs in the playlist are known
- at this time, it is possible to prefetch the .ts files
- when the .ts files are requested at a later time, the data is already cached (in memory) and can be returned immediately
- --max-segments is the maximum number of .ts files (ie: video segments) to hold in the cache
- this option is only meaningful when --prefetch is enabled
- when the cache grows larger than this size, the oldest data is removed to make room to store new data
- when this option is not specified:
- default value:
20
- default value:
- --cache-key sets the type of string used for keys in the cache hashtable
- this option is only meaningful when --prefetch is enabled
0
(default):- sequence number of .ts file w/ .ts file extension (ex: "123.ts")
- pros:
- shortest type of string
- makes the log output easiest to read
- cons:
- in the wild, I've encountered video servers that assign each .ts file a unique filename that always terminate with the same static sequence number
- this is a really weird edge case, but this option provides an easy workaround
- in the wild, I've encountered video servers that assign each .ts file a unique filename that always terminate with the same static sequence number
- pros:
- sequence number of .ts file w/ .ts file extension (ex: "123.ts")
1
:- full filename of .ts file
2
:- full URL of .ts file
- -v sets logging verbosity level:
-1
:- silent
0
(default):- show errors only
1
:- show an informative amount of information
2
:- show technical details
3
:- show an enhanced technical trace (useful while debugging unexpected behavior)
git clone "https://github.com/warren-bank/HLS-Proxy.git"
cd "HLS-Proxy"
npm install
# ----------------------------------------------------------------------
# If using a port number >= 1024 on Linux, or
# If using Windows:
# ----------------------------------------------------------------------
npm start [-- [--help] [--version] [--tls] [--host <ip_address>] [--port <number>] [--req-headers <filepath>] [--origin <header>] [--referer <header>] [--useragent <header>] [--header <name=value>] [--req-options <filepath>] [--req-secure-honor-server-cipher-order] [--req-secure-ciphers <string>] [--req-secure-protocol <string>] [--req-secure-curve <string>] [--hooks <filepath>] [--prefetch] [--max-segments <number>] [--cache-key <number>] [-v <number>] ]
# ----------------------------------------------------------------------
# https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html
#
# Linux considers port numbers < 1024 to be privileged.
# Use "sudo":
# ----------------------------------------------------------------------
npm run sudo [-- [--help] [--version] [--tls] [--host <ip_address>] [--port <number>] [--req-headers <filepath>] [--origin <header>] [--referer <header>] [--useragent <header>] [--header <name=value>] [--req-options <filepath>] [--req-secure-honor-server-cipher-order] [--req-secure-ciphers <string>] [--req-secure-protocol <string>] [--req-secure-curve <string>] [--hooks <filepath>] [--prefetch] [--max-segments <number>] [--cache-key <number>] [-v <number>] ]
-
print help
npm start -- --help
-
start HTTP proxy at specific host:port
npm start -- --host "192.168.0.100" --port "8080"
-
start HTTPS proxy at specific host:port
npm start -- --host "192.168.0.100" --port "8081" --tls
-
start HTTP proxy at default host:port with escalated privilege
npm run sudo -- --port "80"
-
start HTTPS proxy at default host:port with escalated privilege
npm run sudo -- --port "443" --tls
-
start HTTP proxy at specific port and send custom request headers
headers_file="${TMPDIR}/headers.json"
echo '{"Origin" : "http://XXX:80", "Referer": "http://XXX:80/page.html"}' > "$headers_file"
npm start -- --port "8080" --req-headers "$headers_file"
URL='https://httpbin.org/headers'
URL=$(echo "$URL" | base64)
URL="http://127.0.0.1:8080/${URL}.json"
curl --silent "$URL"
- start HTTPS proxy at specific port and send custom request headers
headers_file="${TMPDIR}/headers.json"
echo '{"Origin" : "http://XXX:80", "Referer": "http://XXX:80/page.html"}' > "$headers_file"
npm start -- --port "8081" --req-headers "$headers_file" --tls -v 1
URL='https://127.0.0.1:8081/aHR0cHM6Ly9odHRwYmluLm9yZy9oZWFkZXJzCg==.json'
curl --silent --insecure "$URL"
- start HTTPS proxy at specific port and send custom request headers
h_origin='http://XXX:80'
h_referer='http://XXX:80/page.html'
h_useragent='Chromium'
h_custom_1='X-Foo: 123'
h_custom_2='X-Bar: baz'
npm start -- --port "8081" --origin "$h_origin" --referer "$h_referer" --useragent "$h_useragent" --header "$h_custom_1" --header "$h_custom_2" --tls -v 1
URL='https://127.0.0.1:8081/aHR0cHM6Ly9odHRwYmluLm9yZy9oZWFkZXJzCg==.json'
curl --silent --insecure "$URL"
- identical to the command-line binary
- when playing the proxied HLS video stream in an HTML5 player in a Chromium web browser (ex: THEOplayer)
- if the page hosting the HTML5 video player is served from HTTPS:
- when running only the HTTP proxy server:
- the XHR requests from the player to the HTTP proxy server raise a security warning (insecure content)
- the XHR requests get elevated to HTTPS, which are unanswered (since the HTTPS proxy server isn't running)
- when running only the HTTPS proxy server:
- the XHR requests from the player to the HTTPS proxy server will silently fail
- this is because the HTTPS proxy server is using a self-signed security certificate
- this certificate needs to be (temporarily) allowed
- once it is, the video stream works perfectly
- to allow the certificate:
- browse to a URL hosted by the proxy server ( example )
- you should see the warning:
NET::ERR_CERT_AUTHORITY_INVALID
Your connection is not private - click:
Advanced
- click:
Proceed to 127.0.0.1 (unsafe)
- done
- to allow the certificate:
- when running only the HTTP proxy server:
- if the page hosting the HTML5 video player is served from HTTPS:
- when playing the proxied HLS video stream on a Chromecast
- the HTTP proxy server works perfectly
- the HTTPS proxy server doesn't begin playback
- not sure why..
- probably has something to do with the Chromecast's browser security policies
- a more respectable security certificate (ie: more expensive) would probably fix it
-
error:
ssl3_check_cert_and_algorithm:dh key too small
- attempted fix:
--req-secure-ciphers "AES128-SHA"
- attempted fix:
-
error:
SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
- attempted fix:
--req-secure-protocol "SSLv3_method"
- result:
Error: SSLv3 methods disabled
- issue:
- result:
- attempted fix:
--req-secure-curve "auto"
- attempted fix:
-
- consists of 2 parts:
- a Chromium web browser extension (.crx)
- on each browser tab, it's silently watching the URL of all outbound requests
- every requested URL matching a regex pattern that identifies it to be a video file is displayed in the modal window that toggles open when the extension's icon is clicked
- links in this modal window open to URLs of component #2
- a static website
- a Chromium web browser extension (.crx)
- consists of 2 parts:
-
- provides a simple way to keep and organize bookmarks
- my recipe of favorite video stream servers
- some require "Webcast-Reloaded" to intercept the .m3u8 URL
- some require "Webcast-Reloaded" to intercept the .m3u8 URL, and "HLS-Proxy" to enable casting the stream to Chromecast
- some of the .m3u8 URLs are static, enabling the bookmark to directly load the video on the "Webcast-Reloaded" website
- my recipe of favorite video stream servers
- provides a simple way to keep and organize bookmarks
-
- a Chromium browser extension (user script) for a particular website that hosts many excellent video streams
- removes visual clutter and prevents their site from stealing CPU cycles
-
- a Chromium browser extension (user script) for a particular website that hosts many excellent video streams
- uses their XHR search form to dynamically request a lot of channels, and then filters the results to only display the ones that can be watched for free
-
- a Chromium browser extension (user script) for a particular website that hosts many excellent video streams of live MLB sporting events
- moves the embedded iframe containing the video player to the topmost window, and displays information about the video stream in a modal alert box as well as the console log
-
- a Chromium browser extension (user script) for a particular website that hosts many excellent video streams
- removes visual clutter and busts through their paywall (like: Kool-Aid Man)
- Streamlink
- notes:
- this project has way more features, and is way more polished
- though its main purpose is to transcode online video with ffmpeg and pipe the output into another program, it can be configured to not load a video player and instead start a web server
- it can strongly support individual websites through single-purpose plugins
- it can also support streams via direct URLs
- using URLs from the wild will have mixed results, since cookies and headers and authentication aren't being managed by any plugin
- docs:
- binaries:
- Windows portable
- minimum system requirements:
- Windows 7 SP1
- .NET Framework 4.5
- minimum system requirements:
- Windows portable
- usage test:
streamlink --player-external-http --player-external-http-port 8080 --default-stream best --http-ignore-env --http-no-ssl-verify --url "https://XXX/video.m3u8"
- usage test result:
- doesn't appear to work with HTML5 video players or Chromecast
- the server starts and works as it was intended, but something about the format of the data it "streams" is incompatible
- VLC can play the video stream from the server, and be used to render the video on Chromecast
- notes:
- copyright: Warren Bank
- license: GPL-2.0