Skip to main content

Entitlement

MobileWebTV Platforms
Android logoiOS logoResponsive Web logoWebTV logoAndroidTV logoTvOS logoRoku logo

The Diva player offers support for entitlement check and content protection. This makes easier to integrate the player with commerce systems, user management systems or user account validators. The purpose of the entitlement check integration is to verify whether a certain video content can be played or not, and display a proper message to the user (or a call to action) when it cannot. This happens by passing the user token credential (i.e., an authentication or authorization cookie) to the underlying entitlement system, along with a set of video indicators and IDs that make possible to implement promotions, business checks, or per-content authorizations.

Entitlement Provider​

Calls to the Entitlement BE service is not bundled inside DIVA.
The player needs a Provider that acts as an interface between DIVA and the entitlement service.
The entitlement provider contains request and getConfiguration functions.

note

getConfiguration should return a callback/observable (depending on the platform) so that DIVA can react on hearbeatInterval changes.

Workflow​

  • Project provides an instance of EntitlementProvider.
  • DIVA receives the instance of entitlementProvider in configuration.
  • At each manifest change DIVA calls entitlementProvider request function to perform a tokenization call.
    • In case of successfull response, data coming from entitlement will be used for DRM feature.
    • In case of error, DIVA will display it with the specific error code.
  • If entitlementProvider.getConfiguration() return a heartbeatInterval > 0, DIVA will start calling hearbeat requests through entitlementProvider.request function.

Entitlement Provider samples​

// Type definition
export interface EntitlementConfiguration {
heartBeatInterval: number;
heartbeatSeekInterval?: number;
}

export type EntitlementProvider = {
getConfiguration: (cb: (cf: EntitlementConfiguration) => void) => void;
request: (request: EntitlementRequest) => Promise<EntitlementSuccessResponse>;
};

export interface EntitlementSuccessResponse {
contentUrl: string;
drmData: {
licenseUrl: string;
token: string;
};
heartBeatInterval: number;
}

export interface EntitlementRequest {
type: EntitlementRequestType;
videoMetadata: VideoMetadataClean;
videoSource: VideoSourceClean;
}

export type EntitlementRequestType = 'processUrl' | 'heartBeat';

export declare const EntitlementRequestType: {
processUrl: EntitlementRequestType;
heartBeat: EntitlementRequestType;
};

// Implementation example
const parameters = {
processingUrlCallPath: "https://<entitlement_service_url>",
secretTxt: "testpassword",
Other: "{SharedSecret: '<123ABC123ABC>', SignatureCheck: 'true', Window: 20, TokenName: 'hdnts'}",
heartBeatCallPath: "https://<entitlement_service_url>",
heartBeatInterval: "10000",
heartbeatSeekInterval: "10000";
}

let configuration: EntitlementConfiguration = {
heartBeatInterval: Number.parseInt(parameters.heartBeatInterval) ?? 0,
heartbeatSeekInterval: Number.parseInt(parameters.heartbeatSeekInterval),
};

let configChangeCb: (cf: EntitlementConfiguration) => void;

const getConfiguration = (cb: (cf: EntitlementConfiguration) => void) => {
configChangeCb = cb;
cb(configuration);
};


const request = (request: EntitlementRequest): Promise<EntitlementSuccessResponse> => {
return new Promise((resolve, reject) => {
const url =
request.type === EntitlementRequestType.processUrl
? parameters.processingUrlCallPath
: parameters.heartBeatCallPath;
if (url) {
const data: any = {
Type: request.type === EntitlementRequestType.processUrl ? 1 : 2,
User: '',
VideoId: request.videoMetadata.videoId,
VideoSource: request.videoSource.uri,
VideoKind: request.videoMetadata.assetState === AssetState.Live ? 'live' : 'replay',
AssetState: request.videoMetadata.assetState === AssetState.Live ? '2' : '3',
PlayerType: 'HTML5',
VideoSourceFormat: request.videoSource.format,
VideoSourceName: request.videoSource.format,
DRMType: request.videoSource.drm.type,
AuthType: !!request.videoMetadata.customAttributes.contentKeyData ? 'Token' : 'Open',
ContentKeyData:
request.videoMetadata.customAttributes[
request.videoSource.format.toLowerCase() === 'hls' ? 'contentKeyDataFairPlay' : 'contentKeyData'
],
VideoOfferType: request.videoMetadata.customAttributes.v_offer_type,
...parameters,
};
try {
// Hack for our very specific Entitlement Service
// Force Signature false for WEB
// eslint-disable-next-line no-eval
data.Other = data.Other ? eval(`(${data.Other})`) : undefined;
data.Other.SignatureCheck = 'false';
data.Other = data.Other ? JSON.stringify(data.Other).replace(/"/gi, "'") : undefined;
} catch (e) { }

fetch(url, {
method: 'POST',
body: JSON.stringify(data),
})
.then(async (response) => {
const resp = await response.json();
if (resp.ResponseCode === 0 && `${resp.Response}`.toLowerCase() === 'ok') {
resolve({
contentUrl: resp.ContentUrl,
drmData: {
licenseUrl: resp.LicenseURL,
token: resp.AuthToken,
},
heartBeatInterval: resp.HeartBeatTime * 1000,
});
} else {
// Here integrator can react on entitlement error
reject(new Error(`${resp.ResponseCode}`.padStart(2, '0')));
}
})
.catch((error) => {
// Here integrator can react on entitlement call error
console.log(error);
reject(new Error('22'));
});
} else {
// If entitlement URL missing or empty, pass through
resolve({
contentUrl: request.videoSource.uri,
drmData: {
licenseUrl: request.videoSource.drm.licenseUrl,
token: request.videoSource.drm.token,
},
heartBeatInterval: configuration.heartBeatInterval,
});
}
});
};

/**
* If the project needs to update the heartbeat interval at runtime
* @param config
*/
export const setEntitlementConfiguration = (config: EntitlementConfiguration): void => {
configuration = config;
configChangeCb?.(config);
};

export const entitlementProvider: EntitlementProvider = {
getConfiguration,
request,
};