It's a Nginx+Lua library, strongly inspired by the CBS Bakery, to modify media manifests on-the-fly, like HLS and MPEG Dash. It acts as a proxy between (or in) the frontend
and your origin
.
This is very useful due to the fact that some devices can't play with a higher bitrate, multiple codecs, multiple frame rate, and so on.
# run the nginx
docker run --rm -it -p 8080:8080 leandromoreira/resty-bakery
# fetch unfiltered manifest
curl "http://localhost:8080/media/generic_master.m3u8"
# fetch renditions (variants) with bandwidth >= 800000 and with frame rate = 60 or 60/1
curl "http://localhost:8080/media/b(800000)fps(60,60:1)/generic_master.m3u8"
- Bandwidth (/path/to/media/f/
b(min,max)
/manifes.m3u8) - filters based on uri path following the formatb(min bandwidth, max bandwidth)
. - Framerate (/path/to/media/f/
fps(30,30000:1001)
/manifes.m3u8) - filters out based on uri path following the formatb(list of frame rates)
.
# Let's suppose our origin hosts media at /media/<manifest>.<extension>
# So what we need to do is to set up a location to act like a proxy
# Also, let's say we're going to use /media/<filters>/<manifest>.<extension> to pass the filters
# ex: /media/b(1500000)/playlist.m3u8
location /media {
proxy_pass http://origin;
# we need to keep the original uri with its state, since we're going to rewrite
# from /media/<filters>/<manifest>.<extension> to /media/<manifest>.<extension>
set_by_lua_block $original_uri { return ngx.var.uri }
# when the Lua code may change the length of the response body,
# then it is required to always clear out the Content-Length
header_filter_by_lua_block { ngx.header.content_length = nil }
# rewriting to the proper origin uri, effectively removing the filters
rewrite_by_lua_block {
local uri = ngx.re.sub(ngx.var.uri, "^/media/(.*)/(.*)$", "/media/$2")
ngx.req.set_uri(uri)
}
# applying the filters (after the proxy/upstream has replied)
# this is where the magic happens
body_filter_by_lua_block {
local modified_manifest = bakery.filter(ngx.var.original_uri, ngx.arg[1])
ngx.arg[1] = modified_manifest
ngx.arg[2] = true
}
}
make run
# open another tab
# unmodified manifest
curl -v "http://localhost:8080/media/ffmpeg_master.m3u8"
curl -v "http://localhost:8080/media/ffmpeg_dash.mpd"
# filters out renditions with bandwidth < 1500000
curl -v "http://localhost:8080/media/b(1500000)/ffmpeg_master.m3u8"
curl -v "http://localhost:8080/media/b(1500000)/ffmpeg_dash.mpd"
# filters out renditions with bandwidth > 1500000
curl -v "http://localhost:8080/media/b(0,1500000)/ffmpeg_master.m3u8"
curl -v "http://localhost:8080/media/b(0,1500000)/ffmpeg_dash.mpd"
# filters bandwidth > 800000 and excludes frame rate 60
curl "http://localhost:8080/media/b(800000)fps(60)/generic_master.m3u8"
curl "http://localhost:8080/media/b(800000)fps(60,60:1)/generic_dash.mpd"
# PS: there is an mmedia location just to provide manifests without
# data transfer them in chunked encoding mechanism
# this example was done due some CTV that can't handle chunked transfer encoding
make test
make lint