Skip to content

An Nginx+Lua library to modify media manifests like HLS and MPEG Dash, acting like a proxy.

License

Notifications You must be signed in to change notification settings

leandromoreira/resty-bakery

Repository files navigation

Build Status license

Resty-Bakery [WIP]

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.

Getting started

# 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"

Filters

  • Bandwidth (/path/to/media/f/b(min,max)/manifes.m3u8) - filters based on uri path following the format b(min bandwidth, max bandwidth).
  • Framerate (/path/to/media/f/fps(30,30000:1001)/manifes.m3u8) - filters out based on uri path following the format b(list of frame rates).

Nginx usage example

# 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
        }
    }

Test locally

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

Tests

make test

Lint

make lint