Skip to content

Commit 5fc5cbb

Browse files
Merge pull request #396 from THEOplayer/feature/mediakind-drm
Feature/mediakind drm
2 parents 8e05e52 + daf9377 commit 5fc5cbb

7 files changed

Lines changed: 206 additions & 0 deletions

.changeset/legal-guests-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@theoplayer/react-native-drm': minor
3+
---
4+
5+
Added integration for MediaKind DRM.

drm/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,6 @@ the following connectors, readily available to be used by `react-native-theoplay
6363
| Axinom | ✓ | ✗ | ✓ |
6464
| CastLabs | ✓ | ✓ | ✓ |
6565
| PallyCon | ✗ | ✗ | ✓ |
66+
| MediaKind | ✓ (*) | ✓ (*) | ✓ |
6667

6768
(*) No connector is needed for these DRM vendors; it is handled by the player's default DRM flow.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { DRMConfiguration } from 'react-native-theoplayer';
2+
3+
/**
4+
* The identifier of the MediaKind integration.
5+
*/
6+
export type MediaKindIntegrationID = 'mediakind';
7+
8+
/**
9+
* Describes the configuration of the MediaKind DRM integration.
10+
*
11+
* ```
12+
* const drmConfiguration = {
13+
* integration : 'mediakind',
14+
* fairplay: {
15+
* certificateURL: 'yourMediaKindCertificateUrl',
16+
* licenseAcquisitionURL: 'yourMediaKindLicenseAcquisitionURL'
17+
* }
18+
* }
19+
* ```
20+
*/
21+
export interface MediaKindDrmConfiguration extends DRMConfiguration {
22+
/**
23+
* The identifier of the DRM integration.
24+
*/
25+
integration: MediaKindIntegrationID;
26+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type {
2+
CertificateRequest,
3+
CertificateResponse,
4+
ContentProtectionIntegration,
5+
LicenseRequest,
6+
LicenseResponse,
7+
MaybeAsync,
8+
} from 'react-native-theoplayer';
9+
import {
10+
fromUint8ArrayToString,
11+
fromBase64StringToUint8Array,
12+
fromStringToUint8Array,
13+
fromUint8ArrayToBase64String,
14+
BufferSource,
15+
} from 'react-native-theoplayer';
16+
import type { MediaKindDrmConfiguration } from './MediaKindDrmConfiguration';
17+
import { getFairplayCertificateURL, getFairplayLicenseAcquisitionURL } from '../utils/drmUtils';
18+
19+
export class MediaKindDrmFairplayContentProtectionIntegration implements ContentProtectionIntegration {
20+
static readonly DEFAULT_CERTIFICATE_URL = 'insert default certificate url here';
21+
static readonly DEFAULT_LICENSE_URL = 'insert default license url here';
22+
23+
private readonly contentProtectionConfiguration: MediaKindDrmConfiguration;
24+
private contentId: string | undefined = undefined;
25+
26+
constructor(configuration: MediaKindDrmConfiguration) {
27+
this.contentProtectionConfiguration = configuration;
28+
}
29+
30+
onCertificateRequest(request: CertificateRequest): MaybeAsync<Partial<CertificateRequest> | BufferSource> {
31+
request.url = getFairplayCertificateURL(
32+
this.contentProtectionConfiguration,
33+
MediaKindDrmFairplayContentProtectionIntegration.DEFAULT_CERTIFICATE_URL,
34+
);
35+
return request;
36+
}
37+
38+
onCertificateResponse(response: CertificateResponse): MaybeAsync<BufferSource> {
39+
let certificate = fromUint8ArrayToString(response.body);
40+
const match = certificate.match(/<cert>([\s\S]*?)<\/cert>/);
41+
if (match) {
42+
certificate = match[1].replace(/\s/g, '');
43+
}
44+
return fromBase64StringToUint8Array(certificate);
45+
}
46+
47+
onLicenseRequest(request: LicenseRequest): MaybeAsync<Partial<LicenseRequest> | BufferSource> {
48+
request.url = getFairplayLicenseAcquisitionURL(
49+
this.contentProtectionConfiguration,
50+
MediaKindDrmFairplayContentProtectionIntegration.DEFAULT_LICENSE_URL,
51+
);
52+
if (request.body !== null) {
53+
const spc = fromUint8ArrayToBase64String(request.body);
54+
request.body = fromStringToUint8Array(spc);
55+
}
56+
return request;
57+
}
58+
59+
onLicenseResponse(response: LicenseResponse): MaybeAsync<BufferSource> {
60+
let license = fromUint8ArrayToString(response.body);
61+
const match = license.match(/<ckc>([\s\S]*?)<\/ckc>/);
62+
if (match) {
63+
license = match[1].replace(/\s/g, '');
64+
}
65+
return fromBase64StringToUint8Array(license);
66+
}
67+
68+
extractFairplayContentId(skdUrl: string): string {
69+
// skdUrl without the "skd://" scheme
70+
this.contentId = skdUrl.substring(6);
71+
return this.contentId;
72+
}
73+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'react-native-theoplayer';
2+
import type { MediaKindDrmConfiguration } from './MediaKindDrmConfiguration';
3+
import { MediaKindDrmFairplayContentProtectionIntegration } from './MediaKindDrmFairplayContentProtectionIntegration';
4+
5+
export class MediaKindDrmFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory {
6+
build(configuration: MediaKindDrmConfiguration): ContentProtectionIntegration {
7+
return new MediaKindDrmFairplayContentProtectionIntegration(configuration);
8+
}
9+
}

drm/src/mediakind/barrel.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './MediaKindDrmConfiguration';
2+
export * from './MediaKindDrmFairplayContentProtectionIntegration';
3+
export * from './MediaKindDrmFairplayContentProtectionIntegrationFactory';

drm/src/utils/drmUtils.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type {
2+
DRMConfiguration,
3+
FairPlayKeySystemConfiguration,
4+
WidevineKeySystemConfiguration,
5+
PlayReadyKeySystemConfiguration,
6+
KeySystemConfiguration,
7+
} from 'react-native-theoplayer';
8+
9+
/**
10+
* Appends query parameters to a URL, merging global DRM-level params with key-system-specific params.
11+
* Key-system-specific params take precedence over global params; no duplicate keys are added.
12+
*
13+
* @param url - The base URL to append query parameters to.
14+
* @param config - The top-level DRMConfiguration containing optional global `queryParameters`.
15+
* @param keySystemConfig - The key-system-specific configuration containing optional `queryParameters`.
16+
* @returns The URL with merged query parameters appended.
17+
*/
18+
function appendQueryParameters(url: string, config: DRMConfiguration, keySystemConfig: KeySystemConfiguration | undefined): string {
19+
const merged: { [key: string]: any } = {
20+
...config.queryParameters,
21+
...keySystemConfig?.queryParameters,
22+
};
23+
24+
const entries = Object.entries(merged);
25+
if (entries.length === 0) {
26+
return url;
27+
}
28+
29+
const separator = url.includes('?') ? '&' : '?';
30+
const queryString = entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
31+
return `${url}${separator}${queryString}`;
32+
}
33+
34+
/**
35+
* Returns the FairPlay certificate URL from the DRM configuration with query parameters appended.
36+
*
37+
* @param config - The DRM configuration.
38+
* @param defaultUrl - Fallback URL if no certificateURL is set on the fairplay config.
39+
* @returns The resolved certificate URL with merged query parameters.
40+
*/
41+
export function getFairplayCertificateURL(config: DRMConfiguration & { fairplay?: FairPlayKeySystemConfiguration }, defaultUrl: string): string {
42+
const baseUrl = config.fairplay?.certificateURL ?? defaultUrl;
43+
return appendQueryParameters(baseUrl, config, config.fairplay);
44+
}
45+
46+
/**
47+
* Returns the FairPlay license acquisition URL from the DRM configuration with query parameters appended.
48+
*
49+
* @param config - The DRM configuration.
50+
* @param defaultUrl - Fallback URL if no licenseAcquisitionURL is set on the fairplay config.
51+
* @returns The resolved license acquisition URL with merged query parameters.
52+
*/
53+
export function getFairplayLicenseAcquisitionURL(
54+
config: DRMConfiguration & { fairplay?: FairPlayKeySystemConfiguration },
55+
defaultUrl: string,
56+
): string {
57+
const baseUrl = config.fairplay?.licenseAcquisitionURL ?? defaultUrl;
58+
return appendQueryParameters(baseUrl, config, config.fairplay);
59+
}
60+
61+
/**
62+
* Returns the Widevine license acquisition URL from the DRM configuration with query parameters appended.
63+
*
64+
* @param config - The DRM configuration.
65+
* @param defaultUrl - Fallback URL if no licenseAcquisitionURL is set on the widevine config.
66+
* @returns The resolved license acquisition URL with merged query parameters.
67+
*/
68+
export function getWidevineLicenseAcquisitionURL(
69+
config: DRMConfiguration & { widevine?: WidevineKeySystemConfiguration },
70+
defaultUrl: string,
71+
): string {
72+
const baseUrl = config.widevine?.licenseAcquisitionURL ?? defaultUrl;
73+
return appendQueryParameters(baseUrl, config, config.widevine);
74+
}
75+
76+
/**
77+
* Returns the PlayReady license acquisition URL from the DRM configuration with query parameters appended.
78+
*
79+
* @param config - The DRM configuration.
80+
* @param defaultUrl - Fallback URL if no licenseAcquisitionURL is set on the playready config.
81+
* @returns The resolved license acquisition URL with merged query parameters.
82+
*/
83+
export function getPlayReadyLicenseAcquisitionURL(
84+
config: DRMConfiguration & { playready?: PlayReadyKeySystemConfiguration },
85+
defaultUrl: string,
86+
): string {
87+
const baseUrl = config.playready?.licenseAcquisitionURL ?? defaultUrl;
88+
return appendQueryParameters(baseUrl, config, config.playready);
89+
}

0 commit comments

Comments
 (0)