Delivery

How to deliver and stream media assets from the Nomad Media platform.

Delivery

Secure Video Playback

Video content that is secured can only be watched with the appropriate browser cookies set. There are 3 cookies and they are returned by a specific API call for a specific video or live channel asset.

Retrieving Cookies for a Video Asset

Use the following to get CloudFront cookies for a specific HLS or Dash stream (relatedAssetId) for a specific assetId. An auth token obtained from login is required.

export default async function getRelatedVideoCookies(assetId, relatedAssetId, authToken) {
    const headers = new Headers();
    headers.append("Authorization", `Bearer ${authToken}`);

    const response = await fetch(`${prjConstants.SERVER_URL}/admin/asset/${assetId}/set-cookies/${relatedAssetId}`, {
        method: "GET",
        headers: headers,
    });

    if (response.ok) {
        const cookies = await response.json();
        return cookies;
    }

    console.log(`Get Related Video Cookies failed: ${response.statusText}`);
}

Retrieving Cookies for a Live Channel

export default async function getLiveChannelCookies(liveChannelId, authToken) {
    const headers = new Headers();
    headers.append("Authorization", `Bearer ${authToken}`);

    const response = await fetch(`${prjConstants.SERVER_URL}/liveChanel/${liveChannelId}/set-cookies/`, {
        method: "GET",
        headers: headers,
    });

    if (response.ok) {
        const cookies = await response.json();
        return cookies;
    }

    console.log(`Get Live Channel Cookies failed: ${response.statusText}`);
}

Setting Cookies in the Browser

if (cookies) {
    cookies.forEach((cookie) => {
        document.cookie = cookie;
    });

    player.reset();

    player.src({
        src: fullUrl,
        type: "application/x-mpegURL",
        withCredentials: true,
    });
}

Debugging tip: If secure media still does not play, it is almost always due to cookies not being set correctly — either a JavaScript issue or a server domain configuration issue.

Step 1: Test with a known public folder asset first (without secure cookies) to confirm the player and HLS link work correctly.

Step 2: If the player works for public content but not secure content, contact [email protected] with an example set-cookies API call so the domain configuration can be validated.

Bitmovin Player Configuration

The Bitmovin player requires additional config settings to load secure content:

{
  "options": {
    "withCredentials": true,
    "manifestWithCredentials": true,
    "hlsWithCredentials": true,
    "hlsManifestWithCredentials": true,
    "dashWithCredentials": true,
    "dashManifestWithCredentials": true
  }
}

Bitmovin also supports overriding the TS segment URL to include CloudFront security parameters in each individual request. This is enabled by default in the Nomad Media embedded player and Nomad Media applications:

const playerConfig = {
    network: {
        preprocessHttpRequest: (type, request) => {
            request = this.applyQueryParamsToRequests(type, request);
            return Promise.resolve(request);
        }
    }
};

private bitmovinSearchParams = new URLSearchParams();

private applyQueryParamsToRequests(type, request) {
    const url = new URL(request.url);

    url.searchParams.forEach((value, key) => {
        if (!this.bitmovinSearchParams.has(key)) {
            this.bitmovinSearchParams.set(key, value);
        }
    });

    this.bitmovinSearchParams.forEach((value, key) => {
        if (!url.searchParams.has(key)) {
            url.searchParams.set(key, value);
        }
    });

    request.url = url.href;
    return request;
}

Video.js Player Configuration

Video.js supports overriding the TS segment URL to include CloudFront security parameters:

vjsQueryString = '';

const player = videojs('my-video');

videojs.Vhs.xhr.beforeRequest = (options) => {
    const uri = options.uri;

    if (!this.vjsQueryString && uri?.indexOf('?') !== -1) {
        this.vjsQueryString = uri?.split('?')[1] || '';
    } else if (this.vjsQueryString) {
        options.uri = `${options.uri}?${this.vjsQueryString}`;
    }

    return options;
};

Note: You do not need to use both the VideoJS/Bitmovin override AND the cookies — use one or the other.

CORS Notes

By default, the system has restrictive CORS policies. For testing a sample application, two approaches are available:

  1. Edit CORS policies — Update S3 bucket CORS for {project-prefix}-system-content and {project-prefix}-system-metadata to allow your testing domain. Also update the {project-prefix}-web-api-portal API Gateway CORS policy. Invalidate the CloudFront cache for the content instance after changes. Note: localhost may not work reliably due to videojs cookie behavior.

  2. Use existing site infrastructure — Add a samples folder inside the publish folder in the {project-prefix}-publish bucket. This maps to {project-prefix}.{domain}/samples and inherits the correct CORS configuration automatically.


Embedded Video Player

Basic Embedded Player

The Nomad Media embedded player allows adding Nomad Media functionality to your own application using the <vep-multi-view> web component.

HTML:

<!doctype html>
<html lang="en" class="vep">
<head>
  <meta charset="utf-8">
  <title>Nomad Player Demo</title>
  <base href="">
  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
  <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400&display=swap" rel="stylesheet">
</head>
<body>
  <div id="gtm-script"></div>
  <vep-multi-view vep-config-src='player-4-sidebar.json'></vep-multi-view>
  <script src="runtime.js" defer></script>
  <script src="main.js" defer></script>
</body>
</html>

The key element is <vep-multi-view> — this is where the video player renders. The vep-config-src attribute points to a JSON configuration file. You can also set the config inline using the vep-config attribute.

Player configuration example:

{
  "application": "Test Player 4 No Sidebar",
  "customer": "demo",
  "siteName": "Test Player 4 No Sidebar",
  "hideSidebar": true,
  "googleTagManager": "{put your key here}",
  "players": [{ "format": "hls", "player": "bitmovin" }],
  "applicationId": "a3be3566-a27f-4bae-a9c2-f9e067d66367",
  "bitmovinLicenseKey": "{put your key here}",
  "vepModules": [
    {
      "moduleType": "streams",
      "isEnabled": true,
      "showTileMode": true,
      "streams": [
        {
          "title": "{put the stream title here}",
          "streamUrl": "{put the stream URL here}",
          "autoplay": false
        }
      ]
    }
  ]
}

Embedded Player with React

The Nomad Media embedded player can be used within a React application. A complete sample application is available in the samples-javascript repository.


Anonymous Cookie Access

Nomad Media supports anonymous access to secure CloudFront cookies. This is used when content is publicly available but still requires CloudFront cookies to access — providing an extra security layer to deter direct URL sharing without requiring user authentication.

Request

GET {portalApiUrl}/api/media/set-cookies/{id}

Authorization: Bearer abc...xyz
Content-Type: application/json

The {id} can be one of two values:

ValueDescription
bf8ac754-5b8b-4330-b1aa-76f15fb7f673ContentDefinition ID for LiveChannels — returns 3 CloudFront cookies
3ff29f61-bd0b-4c17-b855-49db5a292aebContentDefinition ID for Assets — returns 3 or 6 cookies (6 if both secured and restricted content is enabled)

Cache the results — cookies enable access to ALL live streams or ALL assets in the system and are valid for the duration of the cookie (normally 4–6 hours). Retrieve once per user session.

JavaScript:

async function getAnonymousCookies(authToken) {
    const headers = new Headers();
    headers.append("Content-Type", "application/json");
    headers.append("Authorization", `Bearer ${authToken}`);

    const response = await fetch(`{portalApiUrl}/api/media/set-cookies/{id}`, {
        method: "GET",
        headers: headers
    });

    if (response.ok) {
        const cookies = await response.json();
        return cookies;
    }

    return undefined;
}

Python:

import json, requests

def get_anonymous_cookies(AUTH_TOKEN: str) -> dict:
    if not AUTH_TOKEN:
        raise Exception("Authentication Token: The authentication token is invalid")

    API_URL = "{portalApiUrl}/api/media/set-cookies/{id}"

    HEADERS = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {AUTH_TOKEN}"
    }

    try:
        RESPONSE = requests.get(API_URL, headers=HEADERS)
        INFO = json.loads(RESPONSE.text)

        if RESPONSE.status_code != 200:
            raise Exception("Response returned" + str(RESPONSE.status_code))

        return INFO
    except:
        raise Exception("Get anonymous cookies failed")

Enabling Anonymous Access

Anonymous access is disabled by default. Enable it in application/nomadSettings:

"application/nomadSettings": {
    "enableAnonymousSecureAssets": true,
    "enableAnonymousSecureLiveStreams": true,
    "liveChannelSecureCookieExpirationHours": 4,
    "AssetSecureCookieExpirationHours": 4
}