DEV Community 👩‍💻👨‍💻

DEV Community 👩‍💻👨‍💻 is a community of 964,423 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Jonas Birmé for Eyevinn Video Dev-Team Blog

Posted on

Open source HLS proxy library for manifest manipulation

There are a number of use cases where HLS manifest manipulation techniques are applicable. Multi CDN switching, server-side ad insertion, looping or truncating a VOD, to mention a few. The fundamental principle is that you need some HLS proxy between the video player and the original HLS you wish to modify somehow. As this is a very common component in all these use cases we have developed and open sourced an NPM library, hls-proxy, that takes care of that. In this post we will describe how to use it and examples of some use cases.

Get started

npm install --save @eyevinn/hls-proxy
Enter fullscreen mode Exit fullscreen mode

Once installed in your NodeJS project you can start by creating a server.js that could look like this:

const { HLSProxy } = require("@eyevinn/hls-proxy");

const handlers = {...};
const proxy = new HLSProxy(handlers);
proxy.listen(process.env.PORT || 8000);
Enter fullscreen mode Exit fullscreen mode

The proxy server is built on fastify.io.

The handlers object implements the following interface:

interface IHandlers {
  originHandler: (request: IProxyRequest) => Promise<string>;
  masterManifestHandler?: (request: IProxyRequest, baseUrl: URL, m3u: any) => Promise<string>;
  mediaManifestHandler?: (request: IProxyRequest, baseUrl: URL, m3u: any) => Promise<string>;
  segmentRedirectHandler?: (request: IProxyRequest, baseUrl: URL) => Promise<string>;
}
Enter fullscreen mode Exit fullscreen mode

originHandler

This handler is called on every request handled by the proxy. The function implementing this handler is expected to return the base URL for where the proxy can fetch the original manifest. For example if the video player requests http://<proxy>/VINN.mp4/master.m3u8 and the originHandler returns https://maitv-vod.lab.eyevinn.technology the proxy will fetch the master manifest from https://maitv-vod.lab.eyevinn.technology/VINN.mp4/master.m3u8.

masterManifestHandler

This handler is called after the proxy has fetched the master manifest from the origin. It provides the original request received by the proxy, base URL set by the originHandler and a parsed M3U object (@eyevinn/m3u8). It is expected to return an HLS master manifest.

mediaManifestHandler

Similar to the masterManifestHandler this is called after the proxy has fetched the manifest from the origin. In this case when the media manifest is retrieved. Also in this case a parsed M3U object is provided to the handler. The handler is expected to return an HLS media manifest.

segmentRedirectHandler

Assuming that the segment URLs are not pointing directly to another server the proxy will receive all requests for segments. If the segment URLs are not rewritten by the mediaManifestHandler this handler needs to be implemented. It is expected to return the URL for where the segment can be found. The proxy will respond with a 302 redirect to the video player.

Example use cases

We will present some example use cases where this proxy can be used.

Multi CDN switching

There might be several reasons to have a multi CDN strategy. It could be cost related, reliability related or just pure business rules related. What ever the reason using an HLS proxy and manifest manipulation is one way of switching between the CDNs. The first example below shows how you can do that with this HLS proxy-library using 302 segment redirects. That gives you the possibility to switch in the middle of the stream even when playing a VOD (the media manifest is only fetched once).

const { HLSProxy } = require("@eyevinn/hls-proxy");

const cdnSelector = () => {
  // Make decision on which CDN that is best to use here
  return "https://maitv-vod.lab.eyevinn.technology";
};

const proxy = new HLSProxy({
  originHandler: async () => {
    return cdnSelector();
  },
  segmentRedirectHandler: async (request, baseUrl) => {
    const redirectUrl = new URL(request.raw.url, baseUrl);
    return redirectUrl.href;
  }
});
proxy.listen(8000);

// Example: http://localhost:8000/VINN.mp4/master.m3u8
Enter fullscreen mode Exit fullscreen mode

Another example that instead rewrites the media manifest have the benefit that the proxy does not have to handle each segment request by the video player, thus reducing the load a bit.

const { HLSProxy } = require("@eyevinn/hls-proxy");

const cdnSelector = () => {
  // Make decision on which CDN that is best to use here
  return "https://maitv-vod.lab.eyevinn.technology";
};

const proxy = new HLSProxy({
  originHandler: async () => {
    return cdnSelector();
  },
  mediaManifestHandler: async (request, baseUrl, m3u) => {
    // rewrite segment URLs to point to chosen CDN
    m3u.items.PlaylistItem.map(item => {
      const newSegmentUri = new URL(request.basePath + item.get("uri"), baseUrl.href);
      item.set("uri", newSegmentUri.href);
    });
    return m3u.toString();
  }
});
proxy.listen(8000);

// Example: http://localhost:8000/VINN.mp4/master.m3u8
Enter fullscreen mode Exit fullscreen mode

Looping a VOD

Let us say that you have a VOD as a slate that you use to fill gaps in a schedule. Instead of having a set of slates with different durations you can have a 3 seconds slate that you loop instead. This is something that also can be achieved using HLS proxy and manifest manipulation.

// Example (2 reps): http://localhost:8000/slate-consuo2.mp4/master.m3u8?r=2

const { HLSProxy } = require("@eyevinn/hls-proxy");
const HLSRepeatVod = require("@eyevinn/hls-repeat");

const proxy = new HLSProxy({
  originHandler: async () => {
    // Origin where the VOD is found
    return "https://maitv-vod.lab.eyevinn.technology";
  },
  masterManifestHandler: async (request, baseUrl, m3u) => {
    const repeats = request.raw.query["r"] || 2; 
    m3u.items.StreamItem.map(item => {
      const params = require("querystring").stringify({
        bw: item.get("bandwidth"),
        r: repeats,
        src: request.raw.url
      });
      item.set("uri", item.get("uri") + "?" + params);
    });
    return m3u.toString();
  },
  mediaManifestHandler: async (request, baseUrl, m3u) => {
    const sourceUrl = new URL(request.raw.query["src"], baseUrl);
    const hlsVod = new HLSRepeatVod(sourceUrl.href, request.raw.query["r"]);
    await hlsVod.load();
    return hlsVod.getMediaManifest(request.raw.query["bw"]);
  },
  segmentRedirectHandler: async (request, baseUrl) => {
    return (new URL(request.raw.url, baseUrl)).href;
  }
});
proxy.listen(8000);
Enter fullscreen mode Exit fullscreen mode



In addition to the HLS proxy library this example uses an HLS manifest manipulation library that creates a new HLS VOD by repeating the contents of another HLS VOD.

Inserting a bumper or ad

Inserting a bumper or pre-roll ad is another use case for the HLS proxy but we are leaving that as an exercise for the reader.

All above mentioned code and libraries are available as open source. More tools and libraries that are open source can be found on our GitHub.

If you need assistance in the development and implementation of this, our team of video developers are happy to help you out. If you have any questions or comments just drop a line in the comments section to this post.

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.