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:
-
Edit CORS policies — Update S3 bucket CORS for
{project-prefix}-system-contentand{project-prefix}-system-metadatato allow your testing domain. Also update the{project-prefix}-web-api-portalAPI Gateway CORS policy. Invalidate the CloudFront cache for the content instance after changes. Note: localhost may not work reliably due to videojs cookie behavior. -
Use existing site infrastructure — Add a
samplesfolder inside thepublishfolder in the{project-prefix}-publishbucket. This maps to{project-prefix}.{domain}/samplesand 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:
| Value | Description |
|---|---|
bf8ac754-5b8b-4330-b1aa-76f15fb7f673 | ContentDefinition ID for LiveChannels — returns 3 CloudFront cookies |
3ff29f61-bd0b-4c17-b855-49db5a292aeb | ContentDefinition 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
}