HLSProxy is fast, low-footprint, HTTP proxy server implemented in C++. Integration test suite done in Python.
Its needed in peer-to-peer streaming system as player's first point of contact where Apple's HTTP live streaming protocol is used.
The proxy function is first step. In the future it may be upgraded to take media segments from peer-to-peer networking layer. For more info about the concept see https://en.wikipedia.org/wiki/Popcorn_Time.
It's not used in production and so far 4 days of work old.
Basic requirements:
- This local server will serve as a relay between VLC and the CDN
- Supporting only HLS media format
- Solution should be as robust as possible
- HLSProxy shall make request to CDN and serve the result to VLC
- Support both relative and absolute URLs in manifest
- The launch procedure:
- Launch HLSProxy server on localhost
- Then start a network video from VLC pointing to your local server
- Required events in the log:
- When a request is intercepted from VLC
- When the CDN answers and we send back the answer to VLC
- Make distinction between a manifest and segment for each new incoming request and served response
- When the server detects the player switched track
VLC GUI and integration test suite screen-cast
Data pipeline: VLC
>> HLSProxy
>> CDN
>> HLSProxy
>> VLC
The processing happens in URL transformation
stage where URI's from CDN's response gets transformed to hit HLSProxy
instead of CDN.
The integration tests shall use libvlc to play each stream in specified file.
Input file containing test streams shall have single URL per line. Blank lines are allowed.
libVLC test will first try to play stream from CDN directly and then test if playback works through HLSProxy
.
Tests that fail to play direct CDN URL will yield "INVALID_URL".
Playback time before integration test concludes successful run shall be different for CDN and HLSProxy
.
We can call transformed url encoded-url
.
CDN url is transformed to connect to HLSProxy
from
plain-url: http://cdn-host:
cdn-port/
path?
query#
fragment to encoded-url: http://
hlsproxy-host:
hlsproxy-port/0~
cdn-host~
cdn-port/
path?
query#
fragment`
And in case of HTTPS:
plain-url: https://cdn-host:
cdn-port/
path?
query#
fragment to encoded-url: http://
hlsproxy-host:
hlsproxy-port/1~
cdn-host~
cdn-port/
path?
query#
fragment`
VLC always use HTTP to connect to HLSProxy
and HLSProxy
then uses HTTPS to connect to CDN.
Transformation steps:
- VLC uses
encoded-url
to create request toHLSProxy
. HLSProxy
decodesencoded-url
and get informationcdn-host
,cdn-port
and whether to use HTTPS or notHLSProxy
usesplain-url
to connect to CDNHLSProxy
streams response from CDN to VLC transforming URLs from playlist fromplain-url
toencoded-url
- VLC completes HTTP transfer and because URLs are transformed next request will also go to
HLSProxy
server
Example:
plain-url: https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8
encoded-url: http://127.0.0.1:8080/1~443~bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8
class HLSProxyServer
accepts new clients and keeps books about connected VLC players. It is possible to direct multiple VLC players on same HLSProxy
server.
class HLSProxyServer
implements threading server. Each new connection is processed in new thread.
Cookies are used to distinguish different players among many requests thay may make. VLC player handles cookies, some other player may ignore cookes.
class HLSClient
take care of VLC request parsing, contacting CDN and parsing CDN's response.
HLSClient::run_player_request_parsing
method is where VLC's request processing takes place. Cookie is read here and if not found new place is created in PlayerActionTracker HLSProxyServer::_player_action_tracker
.
class PlayerActionTracker
is used for keeping global state about players. This allows track-change to be detected.
HLSClient::run_cdn_response_parsing
method is where reponse from CDN is streamed to VLC.
Detecting if content will be HLS playlist can be done in request, in response and in start of the content. Official spec states:
Each Playlist file MUST be identifiable either by the path component
of its URI or by HTTP Content - Type.In the first case, the path MUST
end with either.m3u8 or .m3u.In the second, the HTTP Content - Type
MUST be "application/vnd.apple.mpegurl" or "audio/mpegurl".Clients
SHOULD refuse to parse Playlists that are not so identified.
class HLSClient
does this by checking HTTP requestline to end in ".m3u8" or ".m3u" and later in HLSClient::run_cdn_response_parsing
method by checking Content-Type of the response. In paractice not everyone honors latest spec so several other Content-Type variations are checked. See HLSClient::media_context_type_from_request_url
and HLSClient::media_context_type_from_response_header
.
When status of response is 404 then response is not marked as MANIFEST type. It is normal to detect request as MANIFEST and response UNKNOWN.
If response is not MANIFEST then response is sent unmodified to VLC.
If response is MANIFEST then we need to modify URLs in content. When Content-Length
header is defined by CDN and we modify URLs (we need to add a path segment) the Content-Length
value is no longer correct. Because we decided to stream response to VLC keeping minimal latency we can't know correct Content-Length
value when HTTP headers are sent to VLC. Because of this chunked encoding is used in sending response. Method to do this operation is HLSClient::send_to_player_nossl_chunked
.
At the end of HLSClient::run_cdn_response_parsing
method time measurement is taken. Time is measured from start of VLC's request to end of CDN response streamed to player.
class CDNConnection
is used to make request to CDN and read response from socket. class CDNConnectionSSL
is the same for SSL connection. mbedtls-2.16.2 library is used for SSL in class CDNConnectionSSL
. Version used during dev is https://tls.mbed.org/download/mbedtls-2.16.2-apache.tgz. Just unpack it in root of the project, compiler and linker options should be already set in VC solution file.
HTTP redirects are handled by rewriting Location
header so VLC is left to handle recursive redirect situations.
HTTP request and response parsing is done using http-parser
library from node.js https://github.com/nodejs/http-parser .
Depends on https://tls.mbed.org/download/mbedtls-2.16.2-apache.tgz. Unpack in project dir .
Depends on ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-9-1-release/. Copy in project dir under pthreads-win32/ .
Depends on https://github.com/nodejs/http-parser.
Build using Visual Studio 2017 .
HLSProxy.exe <host-to-listen-on> <port>
Exaple:
HLSProxy.exe 127.0.0.1 8080
Note that 0.0.0.0
is not valid parameter for host
as this is later used to rewrite URLs.
If using VLC UI please note that you must enter encoded-url
so stream goes through HLSProxy
server.
Scripts for integration test are located in test_integration_py/
dir.
Compose list of test HLS sources and place them in a file. There is example file located test_integration_py/hls_sources.txt
.
First start HLSProxy server. For example:
HLSProxy.exe 127.0.0.1 8080
Then start test suire with:
\test_integration_py>python test.py hls_sources.txt 127.0.0.1 8080
Each HLS source will be tested. libVLC will decode media for some time and report results.
Test run complete:
[TestOutcome PASSING http://dzeri.com/tmp/lsv2-1920x1080.mp4]
[TestOutcome PASSING https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8]
[TestOutcome PASSING http://dzeri.com/streamlabs/absolute.m3u8]
[TestOutcome PASSING http://dzeri.com/streamlabs/redirected_absolute.m3u8]
[TestOutcome INVALID_URL http://dzeri.com/s/o/m/e/w/h/e/r/e/notexisting_url.m3u8]
[TestOutcome INVALID_URL http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8]
[TestOutcome INVALID_URL http://www.streambox.fr/playlists/test_001/stream.m3u8]
[TestOutcome FAILED https://mnmedias.api.telequebec.tv/m3u8/29880.m3u8]
Debuggable #########
Maintainable ##########
Understandable ########
Documented #######
Portability ########
Other HTTP media streams may work (like MP4) but are not tested.
IPv6 not supported. Changes to socket logic is needed.
Commandline parsing is not robust.
TCP tuning not done.
Cookie handling is basic. Proper HTTP Cookie parser should be used in the future.