diff --git a/.changeset/khaki-shoes-visit.md b/.changeset/khaki-shoes-visit.md new file mode 100644 index 00000000..8e915708 --- /dev/null +++ b/.changeset/khaki-shoes-visit.md @@ -0,0 +1,5 @@ +--- +"@theoplayer/drm-connector-web": major +--- + +Initial Release. diff --git a/.idea/web-connectors.iml b/.idea/web-connectors.iml index c2d8285e..b9613383 100644 --- a/.idea/web-connectors.iml +++ b/.idea/web-connectors.iml @@ -11,6 +11,8 @@ + + diff --git a/README.md b/README.md index 2b8e841d..c3539fc8 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Using the available connectors allows you to augment the features delivered thro | CMCD | [![@theoplayer/cmcd-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fcmcd-connector-web?label=%40theoplayer%2Fcmcd-connector-web)](https://npmjs.com/package/@theoplayer/cmcd-connector-web) | [cmcd](https://github.com/THEOplayer/web-connectors/tree/main/cmcd) | | Comscore | [![@theoplayer/comscore-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fcomscore-connector-web?label=%40theoplayer%2Fcomscore-connector-web)](https://npmjs.com/package/@theoplayer/comscore-connector-web) | [comscore](https://github.com/THEOplayer/web-connectors/tree/main/comscore) | | Conviva | [![@theoplayer/conviva-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fconviva-connector-web?label=%40theoplayer%2Fconviva-connector-web)](https://npmjs.com/package/@theoplayer/conviva-connector-web) | [conviva](https://github.com/THEOplayer/web-connectors/tree/main/conviva) | +| DRM | [![@theoplayer/drm-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fdrm-connector-web?label=%40theoplayer%2Fdrm-connector-web)](https://npmjs.com/package/@theoplayer/drm-connector-web) | [drm](https://github.com/THEOplayer/web-connectors/tree/main/drm) | | Gemius | [![@theoplayer/gemius-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fgemius-connector-web?label=%40theoplayer%2Fgemius-connector-web)](https://npmjs.com/package/@theoplayer/gemius-connector-web) | [gemius](https://github.com/THEOplayer/web-connectors/tree/main/gemius) | | Nielsen | [![@theoplayer/nielsen-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fnielsen-connector-web?label=%40theoplayer%2Fnielsen-connector-web)](https://npmjs.com/package/@theoplayer/nielsen-connector-web) | [nielsen](https://github.com/THEOplayer/web-connectors/tree/main/nielsen) | | Yospace | [![@theoplayer/yospace-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fyospace-connector-web?label=%40theoplayer%2Fyospace-connector-web)](https://npmjs.com/package/@theoplayer/yospace-connector-web) | [yospace](https://github.com/THEOplayer/web-connectors/tree/main/yospace) | diff --git a/comscore/src/comscore/ComScore.d.ts b/comscore/src/comscore/ComScore.d.ts index de717661..92623965 100644 --- a/comscore/src/comscore/ComScore.d.ts +++ b/comscore/src/comscore/ComScore.d.ts @@ -1,265 +1,263 @@ declare namespace ns_ { namespace analytics { - enum ConnectivityType { - UNKNOWN, - UNAVAILABLE, - DISCONNECTED, - CONNECTED, - ETHERNET, - WIFI, - WWAN, - BLUETOOTH, - EMULATOR - } - - enum PlatformAPIs { - SmartTV, - Netcast, - Cordova, - Trilithium, - AppleTV, - Chromecast, - Xbox, - webOS, - tvOS, - nodejs, - html5, - JSMAF, - Skeleton, - WebBrowser - } - - namespace PlatformApi { - function setPlatformAPI(platformApi: PlatformAPIs): void; - function setPlatformAPI(platformApi: PlatformAPIs, interfaceObject: unknown): void; - function setPlatformApi(platformApi: PlatformAPIs, interfaceObject: unknown): void; - } - - class StreamingAnalytics { - setMediaPlayerName(name: string): void; - setMediaPlayerVersion(version: string): void; - createPlaybackSession(): void - getPlaybackSessionId(): void - loopPlaybackSession(): void - notifyBufferStart(): void - notifyBufferStop(): void - notifyChangePlaybackRate(rate: number): void; - notifyEnd(): void - notifyPause(): void - notifyPlay(): void - notifySeekStart(): void - setDvrWindowLength(length: number): void; - setImplementationId(id: string): void; - setMetadata(metadata: any): void; - setProjectId(id: string): void; - startFromDvrWindowOffset(offset: number): void; - startFromPosition(position: number): void; - startFromSegment(segment: any): void; - constructor(); - } - - namespace StreamingAnalytics { - namespace AdvertisementMetadata { - export enum AdvertisementType { - ON_DEMAND_PRE_ROLL, - ON_DEMAND_MID_ROLL, - ON_DEMAND_POST_ROLL, - LIVE, - BRANDED_ON_DEMAND_PRE_ROLL, - BRANDED_ON_DEMAND_MID_ROLL, - BRANDED_ON_DEMAND_POST_ROLL, - BRANDED_AS_CONTENT, - BRANDED_DURING_LIVE, - OTHER, - } + enum ConnectivityType { + UNKNOWN, + UNAVAILABLE, + DISCONNECTED, + CONNECTED, + ETHERNET, + WIFI, + WWAN, + BLUETOOTH, + EMULATOR + } - export enum AdvertisementDeliveryType { - NATIONAL, - LOCAL, - SYNDICATION - } - } - - class AdvertisementMetadata { - addCustomLabels(labels: any): void; - classifyAsAudioStream(isAudio: boolean): void; - setCallToActionUrl(url: string): void; - setClipUrl(url: string): void; - setDeliveryType(type: StreamingAnalytics.AdvertisementMetadata.AdvertisementDeliveryType): void; - setLength(length: number): void; - setMediaType(type: StreamingAnalytics.AdvertisementMetadata.AdvertisementType): void; - setOwner(owner: string): void; - setPlacementId(id: string): void; - setRelatedContentMetadata(metadata: any): void; - setServer(server: string): void; - setServerCampaignId(id: string): void; - setSiteId(id: string): void; - setTitle(title: string): void; - setUniqueId(id: string): void; - setVideoDimensions(width: number, height: number): void; - - getMetadataLabels(): any; - - - constructor(); - } - - namespace ContentMetadata { - - export enum ContentDeliveryAdvertisementCapability { - NONE, - DYNAMIC_LOAD, - DYNAMIC_REPLACEMENT, - LINEAR_1DAY, - LINEAR_2DAY, - LINEAR_3DAY, - LINEAR_4DAY, - LINEAR_5DAY, - LINEAR_6DAY, - LINEAR_7DAY - } - - export enum ContentDeliveryComposition { - CLEAN, - EMBED - } - - export enum ContentDeliveryMode { - LINEAR, - ON_DEMAND - } - - export enum ContentDeliverySubscriptionType { - ADVERTISING, - PREMIUM, - SUBSCRIPTION, - TRADITIONAL_MVPD, - TRANSACTIONAL, - VIRTUAL_MVPD, - } - - export enum ContentDistributionModel { - EXCLUSIVELY_ONLINE, - TV_AND_ONLINE - } - - export enum ContentFeedType { - EAST_HD, - EAST_SD, - OTHER, - WEST_HD, - WEST_SD - } - - export enum ContentMediaFormat { - EXTRA_EPISODE, - EXTRA_GENERIC, - EXTRA_MOVIE, - FULL_CONTENT_EPISODE, - FULL_CONTENT_GENERIC, - FULL_CONTENT_MOVIE, - PARTIAL_CONTENT_EPISODE, - PARTIAL_CONTENT_GENERIC, - PARTIAL_CONTENT_MOVIE, - PREVIEW_EPISODE, - PREVIEW_GENERIC, - PREVIEW_MOVIE - } - - export enum ContentType { - LONG_FORM_ON_DEMAND, - SHORT_FORM_ON_DEMAND, - LIVE, - USER_GENERATED_SHORT_FORM_ON_DEMAND, - USER_GENERATED_LONG_FORM_ON_DEMAND, - USER_GENERATED_LIVE, - BUMPER, - OTHER, - } + enum PlatformAPIs { + SmartTV, + Netcast, + Cordova, + Trilithium, + AppleTV, + Chromecast, + Xbox, + webOS, + tvOS, + nodejs, + html5, + JSMAF, + Skeleton, + WebBrowser + } + + namespace PlatformApi { + function setPlatformAPI(platformApi: PlatformAPIs): void; + function setPlatformAPI(platformApi: PlatformAPIs, interfaceObject: unknown): void; + function setPlatformApi(platformApi: PlatformAPIs, interfaceObject: unknown): void; } - - class ContentMetadata { - addCustomLabels(labels: any): void; - carryTvAdvertisementLoad(carriesTvAdvertisementLoad: boolean): void; - classifyAsAudioStream(audioStream: boolean): void; - classifyAsCompleteEpisode(completeEpisode: boolean): void; - setClipUrl(url: string): void; - setDateOfDigitalAiring(year: number, month: number, day: number): void; - setDateOfProduction(year: number, month: number, day: number): void; - setDateOfTvAiring(year: number, month: number, day: number): void; - setDeliveryAdvertisementCapability(value: StreamingAnalytics.ContentMetadata.ContentDeliveryAdvertisementCapability): void; - setDeliveryComposition(value: StreamingAnalytics.ContentMetadata.ContentDeliveryComposition): void; - setDeliveryMode(value: StreamingAnalytics.ContentMetadata.ContentDeliveryMode): void; - setDeliverySubscriptionType(value: StreamingAnalytics.ContentMetadata.ContentDeliverySubscriptionType): void; - setDictionaryClassificationC3(value: string): void; - setDictionaryClassificationC4(value: string): void; - setDictionaryClassificationC6(value: string): void; - setDistributionModel(value: StreamingAnalytics.ContentMetadata.ContentDistributionModel): void; - setEpisodeId(id: string): void; - setEpisodeNumber(episodeNumber: string): void; - setEpisodeSeasonNumber(seasonNumber: string): void; - setEpisodeTitle(title: string): void; - setFeedType(value: StreamingAnalytics.ContentMetadata.ContentFeedType): void; - setGenreId(id: string): void; - setGenreName(name: string): void; - setLength(length: number): void; - setMediaFormat(value: StreamingAnalytics.ContentMetadata.ContentMediaFormat): void; - setMediaType(value: StreamingAnalytics.ContentMetadata.ContentType): void; - setNetworkAffiliate(code: string): void; - setPlaylistTitle(title: string): void; - setProgramId(id: string): void; - setProgramTitle(title: string): void; - setPublisherName(name: string): void; - setStationCode(code: string): void; - setStationTitle(title: string): void; - setTimeOfDigitalAiring(hours: number, minutes: number): void; - setTimeOfProduction(hours: number, minutes: number): void; - setTimeOfTvAiring(hours: number, minutes: number): void; - setTotalSegments(total: number): void; - setUniqueId(id: string): void; - setVideoDimensions(width: number, height: number): void; - - getMetadataLabels(): any; - + + class StreamingAnalytics { + setMediaPlayerName(name: string): void; + setMediaPlayerVersion(version: string): void; + createPlaybackSession(): void; + getPlaybackSessionId(): void; + loopPlaybackSession(): void; + notifyBufferStart(): void; + notifyBufferStop(): void; + notifyChangePlaybackRate(rate: number): void; + notifyEnd(): void; + notifyPause(): void; + notifyPlay(): void; + notifySeekStart(): void; + setDvrWindowLength(length: number): void; + setImplementationId(id: string): void; + setMetadata(metadata: any): void; + setProjectId(id: string): void; + startFromDvrWindowOffset(offset: number): void; + startFromPosition(position: number): void; + startFromSegment(segment: any): void; constructor(); + } + + namespace StreamingAnalytics { + namespace AdvertisementMetadata { + export enum AdvertisementType { + ON_DEMAND_PRE_ROLL, + ON_DEMAND_MID_ROLL, + ON_DEMAND_POST_ROLL, + LIVE, + BRANDED_ON_DEMAND_PRE_ROLL, + BRANDED_ON_DEMAND_MID_ROLL, + BRANDED_ON_DEMAND_POST_ROLL, + BRANDED_AS_CONTENT, + BRANDED_DURING_LIVE, + OTHER + } + + export enum AdvertisementDeliveryType { + NATIONAL, + LOCAL, + SYNDICATION + } + } + + class AdvertisementMetadata { + addCustomLabels(labels: any): void; + classifyAsAudioStream(isAudio: boolean): void; + setCallToActionUrl(url: string): void; + setClipUrl(url: string): void; + setDeliveryType(type: StreamingAnalytics.AdvertisementMetadata.AdvertisementDeliveryType): void; + setLength(length: number): void; + setMediaType(type: StreamingAnalytics.AdvertisementMetadata.AdvertisementType): void; + setOwner(owner: string): void; + setPlacementId(id: string): void; + setRelatedContentMetadata(metadata: any): void; + setServer(server: string): void; + setServerCampaignId(id: string): void; + setSiteId(id: string): void; + setTitle(title: string): void; + setUniqueId(id: string): void; + setVideoDimensions(width: number, height: number): void; + + getMetadataLabels(): any; + + constructor(); + } + + namespace ContentMetadata { + export enum ContentDeliveryAdvertisementCapability { + NONE, + DYNAMIC_LOAD, + DYNAMIC_REPLACEMENT, + LINEAR_1DAY, + LINEAR_2DAY, + LINEAR_3DAY, + LINEAR_4DAY, + LINEAR_5DAY, + LINEAR_6DAY, + LINEAR_7DAY + } + + export enum ContentDeliveryComposition { + CLEAN, + EMBED + } + + export enum ContentDeliveryMode { + LINEAR, + ON_DEMAND + } + + export enum ContentDeliverySubscriptionType { + ADVERTISING, + PREMIUM, + SUBSCRIPTION, + TRADITIONAL_MVPD, + TRANSACTIONAL, + VIRTUAL_MVPD + } + + export enum ContentDistributionModel { + EXCLUSIVELY_ONLINE, + TV_AND_ONLINE + } + + export enum ContentFeedType { + EAST_HD, + EAST_SD, + OTHER, + WEST_HD, + WEST_SD + } + export enum ContentMediaFormat { + EXTRA_EPISODE, + EXTRA_GENERIC, + EXTRA_MOVIE, + FULL_CONTENT_EPISODE, + FULL_CONTENT_GENERIC, + FULL_CONTENT_MOVIE, + PARTIAL_CONTENT_EPISODE, + PARTIAL_CONTENT_GENERIC, + PARTIAL_CONTENT_MOVIE, + PREVIEW_EPISODE, + PREVIEW_GENERIC, + PREVIEW_MOVIE + } + + export enum ContentType { + LONG_FORM_ON_DEMAND, + SHORT_FORM_ON_DEMAND, + LIVE, + USER_GENERATED_SHORT_FORM_ON_DEMAND, + USER_GENERATED_LONG_FORM_ON_DEMAND, + USER_GENERATED_LIVE, + BUMPER, + OTHER + } + } + + class ContentMetadata { + addCustomLabels(labels: any): void; + carryTvAdvertisementLoad(carriesTvAdvertisementLoad: boolean): void; + classifyAsAudioStream(audioStream: boolean): void; + classifyAsCompleteEpisode(completeEpisode: boolean): void; + setClipUrl(url: string): void; + setDateOfDigitalAiring(year: number, month: number, day: number): void; + setDateOfProduction(year: number, month: number, day: number): void; + setDateOfTvAiring(year: number, month: number, day: number): void; + setDeliveryAdvertisementCapability( + value: StreamingAnalytics.ContentMetadata.ContentDeliveryAdvertisementCapability + ): void; + setDeliveryComposition(value: StreamingAnalytics.ContentMetadata.ContentDeliveryComposition): void; + setDeliveryMode(value: StreamingAnalytics.ContentMetadata.ContentDeliveryMode): void; + setDeliverySubscriptionType( + value: StreamingAnalytics.ContentMetadata.ContentDeliverySubscriptionType + ): void; + setDictionaryClassificationC3(value: string): void; + setDictionaryClassificationC4(value: string): void; + setDictionaryClassificationC6(value: string): void; + setDistributionModel(value: StreamingAnalytics.ContentMetadata.ContentDistributionModel): void; + setEpisodeId(id: string): void; + setEpisodeNumber(episodeNumber: string): void; + setEpisodeSeasonNumber(seasonNumber: string): void; + setEpisodeTitle(title: string): void; + setFeedType(value: StreamingAnalytics.ContentMetadata.ContentFeedType): void; + setGenreId(id: string): void; + setGenreName(name: string): void; + setLength(length: number): void; + setMediaFormat(value: StreamingAnalytics.ContentMetadata.ContentMediaFormat): void; + setMediaType(value: StreamingAnalytics.ContentMetadata.ContentType): void; + setNetworkAffiliate(code: string): void; + setPlaylistTitle(title: string): void; + setProgramId(id: string): void; + setProgramTitle(title: string): void; + setPublisherName(name: string): void; + setStationCode(code: string): void; + setStationTitle(title: string): void; + setTimeOfDigitalAiring(hours: number, minutes: number): void; + setTimeOfProduction(hours: number, minutes: number): void; + setTimeOfTvAiring(hours: number, minutes: number): void; + setTotalSegments(total: number): void; + setUniqueId(id: string): void; + setVideoDimensions(width: number, height: number): void; + + getMetadataLabels(): any; + + constructor(); + } } - } - - namespace configuration { - function setApplicationName(name: string): void; - - function setApplicationVersion(name: string): void; - - function addClient(config: PublisherConfiguration): void; - - function getPublisherConfiguration(id: string): any; - - function setPersistentLabel(name: string, value: any): void; - - function addPersistentLabels(labels: any): void; - - function enableImplementationValidationMode(): void; - - function enableChildDirectedApplicationMode(): void; - - class PublisherConfiguration { - publisherId: string; - constructor({ }: any) + namespace configuration { + function setApplicationName(name: string): void; + + function setApplicationVersion(name: string): void; + + function addClient(config: PublisherConfiguration): void; + + function getPublisherConfiguration(id: string): any; + + function setPersistentLabel(name: string, value: any): void; + + function addPersistentLabels(labels: any): void; + + function enableImplementationValidationMode(): void; + + function enableChildDirectedApplicationMode(): void; + + class PublisherConfiguration { + publisherId: string; + constructor({}: any); + } } - } - - function notifyHiddenEvent(): void; - - function notifyEnterForeground(): void; - - function notifyExitForeground(): void; - - function close(): void; - - function start(): void; + function notifyHiddenEvent(): void; + + function notifyEnterForeground(): void; + + function notifyExitForeground(): void; + + function close(): void; + + function start(): void; } - } - \ No newline at end of file +} diff --git a/drm/.gitignore b/drm/.gitignore new file mode 100644 index 00000000..bf62141d --- /dev/null +++ b/drm/.gitignore @@ -0,0 +1,24 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +lib/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# THEOplayer build and TypeScript definitions +local/ diff --git a/drm/LICENSE.md b/drm/LICENSE.md new file mode 100644 index 00000000..146db44c --- /dev/null +++ b/drm/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 THEO Technologies NV + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/drm/README.md b/drm/README.md new file mode 100644 index 00000000..2db76dbf --- /dev/null +++ b/drm/README.md @@ -0,0 +1,83 @@ +# drm-connector-web + +A collection of DRM connectors for the THEOplayer Web SDK: +- Axinom DRM +- Microsoft Azure DRM +- CastLabs DRMtoday +- Comcast DRM +- EZDRM (only for Fairplay, as Widevine and PlayReady can use the default implementation) +- Irdeto Control +- BuyDRM KeyOS +- Nagra DRM +- Vualto VuDRM +- Verimatrix MultiDRM Core DRM +- Arris Titanium DRM + +## Installation + +Install using your favorite package manager for Node (such as `npm` or `yarn`): + +### Install via npm + +```bash +npm install @theoplayer/drm-connector-web +``` + +### Install via yarn + +```bash +yarn add @theoplayer/drm-connector-web +``` + +## Usage +First you need to add the one of the DRM connectors to your app. +This example uses Axinom DRM but this equivalent with all connectors: + +* Add as a regular script + +```html + + +``` + +* Add as an ES2015 module + +```html + +``` + +To make use of the registered DRM connectors, you need : + +```javascript +player.source = { + sources: [ + { + src: '', + contentProtection: { + integration: 'axinom', + integrationParameters: { + // This is specific for Axinom. + token: '' + }, + widevine: { ... }, + playready: { ... }, + fairplay: { ... } + } + } + ] +}; +``` diff --git a/drm/package.json b/drm/package.json new file mode 100644 index 00000000..7553f089 --- /dev/null +++ b/drm/package.json @@ -0,0 +1,47 @@ +{ + "name": "@theoplayer/drm-connector-web", + "version": "0.0.0", + "description": "External DRM connectors for the THEOplayer Web SDK", + "main": "dist/drm-connector.umd.js", + "repository": { + "type": "git", + "url": "git+https://github.com/THEOplayer/web-connectors.git", + "directory": "drm" + }, + "bugs": { + "url": "https://github.com/THEOplayer/web-connectors/issues" + }, + "homepage": "https://github.com/THEOplayer/web-connectors/tree/main/drm#readme", + "module": "dist/drm-connector.esm.js", + "types": "dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/drm-connector.esm.js", + "require": "./dist/drm-connector.umd.js" + }, + "./dist/*": "./dist/*", + "./package": "./package.json", + "./package.json": "./package.json" + }, + "scripts": { + "clean": "rimraf lib dist", + "bundle": "rollup -c rollup.config.mjs", + "build": "npm run clean && npm run bundle", + "watch": "npm run bundle -- --watch", + "serve": "http-server ../", + "test": "echo \"No tests yet\"" + }, + "author": "THEO Technologies NV", + "license": "MIT", + "files": [ + "dist/", + "CHANGELOG.md", + "README.md", + "LICENSE.md", + "package.json" + ], + "peerDependencies": { + "theoplayer": "^7.0.0" + } +} diff --git a/drm/rollup.config.mjs b/drm/rollup.config.mjs new file mode 100644 index 00000000..cca4fcf9 --- /dev/null +++ b/drm/rollup.config.mjs @@ -0,0 +1,13 @@ +import fs from "node:fs"; +import {getSharedBuildConfiguration} from "../tools/build.mjs"; + +const {version} = JSON.parse(fs.readFileSync("./package.json", "utf8")); + +const fileName = "drm-connector"; +const globalName = "THEOplayerDrmConnector"; +const banner = ` +/** + * THEOplayer DRM Connectors v${version} + */`.trim(); + +export default getSharedBuildConfiguration({ fileName, globalName, banner }); diff --git a/drm/src/axinomdrm/AxinomDrmConfiguration.ts b/drm/src/axinomdrm/AxinomDrmConfiguration.ts new file mode 100644 index 00000000..91f54466 --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmConfiguration.ts @@ -0,0 +1,29 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Axinom integration. + */ +export type AxinomIntegrationId = 'axinom'; + +/** + * Describes the configuration of the Axinom DRM integration. + */ +export interface AxinomDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: AxinomIntegrationId; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration. + */ + integrationParameters: { + /** + * The Axinom Authorization Token. + * + *
- Token that will be added to the headers of the license request. + */ + token: string; + }; +} diff --git a/drm/src/axinomdrm/AxinomDrmFairplayContentProtectionIntegration.ts b/drm/src/axinomdrm/AxinomDrmFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..bbebd5c6 --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmFairplayContentProtectionIntegration.ts @@ -0,0 +1,38 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource, CertificateRequest } from 'theoplayer'; +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; +import { isAxinomDrmDRMConfiguration } from './AxinomDrmUtils'; + +export class AxinomDrmFairplayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: AxinomDrmConfiguration; + + constructor(configuration: AxinomDrmConfiguration) { + if (!isAxinomDrmDRMConfiguration(configuration)) { + throw new Error('The Axinom token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.certificateURL) { + throw new Error('The Axinom certificate url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.certificateURL; + request.headers = { + ...request.headers, + 'X-AxDRM-Message': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL) { + throw new Error('The Axinom Fairplay license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'X-AxDRM-Message': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } +} diff --git a/drm/src/axinomdrm/AxinomDrmFairplayContentProtectionIntegrationFactory.ts b/drm/src/axinomdrm/AxinomDrmFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..9c0f98ea --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; +import { AxinomDrmFairplayContentProtectionIntegration } from './AxinomDrmFairplayContentProtectionIntegration'; + +export class AxinomDrmFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: AxinomDrmConfiguration): ContentProtectionIntegration { + return new AxinomDrmFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/axinomdrm/AxinomDrmPlayReadyContentProtectionIntegration.ts b/drm/src/axinomdrm/AxinomDrmPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..a76e3e16 --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,26 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; +import { isAxinomDrmDRMConfiguration } from './AxinomDrmUtils'; + +export class AxinomDrmPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: AxinomDrmConfiguration; + + constructor(configuration: AxinomDrmConfiguration) { + if (!isAxinomDrmDRMConfiguration(configuration)) { + throw new Error('The Axinom token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.playready?.licenseAcquisitionURL) { + throw new Error('The PlayReady Axinom license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.playready?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'X-AxDRM-Message': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } +} diff --git a/drm/src/axinomdrm/AxinomDrmPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/axinomdrm/AxinomDrmPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..cff52341 --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; +import { AxinomDrmPlayReadyContentProtectionIntegration } from './AxinomDrmPlayReadyContentProtectionIntegration'; + +export class AxinomDrmPlayReadyContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: AxinomDrmConfiguration): ContentProtectionIntegration { + return new AxinomDrmPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/axinomdrm/AxinomDrmUtils.ts b/drm/src/axinomdrm/AxinomDrmUtils.ts new file mode 100644 index 00000000..8b70580c --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmUtils.ts @@ -0,0 +1,5 @@ +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; + +export function isAxinomDrmDRMConfiguration(configuration: AxinomDrmConfiguration): boolean { + return configuration.integrationParameters.token !== undefined; +} diff --git a/drm/src/axinomdrm/AxinomDrmWidevineContentProtectionIntegration.ts b/drm/src/axinomdrm/AxinomDrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..2ca29274 --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,26 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; +import { isAxinomDrmDRMConfiguration } from './AxinomDrmUtils'; + +export class AxinomDrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: AxinomDrmConfiguration; + + constructor(configuration: AxinomDrmConfiguration) { + if (!isAxinomDrmDRMConfiguration(configuration)) { + throw new Error('The Axinom token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL) { + throw new Error('The Widevine Axinom license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'X-AxDRM-Message': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } +} diff --git a/drm/src/axinomdrm/AxinomDrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/axinomdrm/AxinomDrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..a9ca6bbf --- /dev/null +++ b/drm/src/axinomdrm/AxinomDrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { AxinomDrmConfiguration } from './AxinomDrmConfiguration'; +import { AxinomDrmWidevineContentProtectionIntegration } from './AxinomDrmWidevineContentProtectionIntegration'; + +export class AxinomDrmWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: AxinomDrmConfiguration): ContentProtectionIntegration { + return new AxinomDrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/axinomdrm/barrel.ts b/drm/src/axinomdrm/barrel.ts new file mode 100644 index 00000000..e1445306 --- /dev/null +++ b/drm/src/axinomdrm/barrel.ts @@ -0,0 +1,9 @@ +export * from './AxinomDrmConfiguration'; +export * from './AxinomDrmUtils'; + +export * from './AxinomDrmFairplayContentProtectionIntegration'; +export * from './AxinomDrmFairplayContentProtectionIntegrationFactory'; +export * from './AxinomDrmPlayReadyContentProtectionIntegration'; +export * from './AxinomDrmPlayReadyContentProtectionIntegrationFactory'; +export * from './AxinomDrmWidevineContentProtectionIntegration'; +export * from './AxinomDrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/azuredrm/AzureDrmConfiguration.ts b/drm/src/azuredrm/AzureDrmConfiguration.ts new file mode 100644 index 00000000..8f847686 --- /dev/null +++ b/drm/src/azuredrm/AzureDrmConfiguration.ts @@ -0,0 +1,29 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Azure Media Services integration. + */ +export type AzureIntegrationID = 'azure'; + +/** + * Describes the configuration of the Azure Media Services DRM integration. + */ +export interface AzureDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: AzureIntegrationID; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration. + */ + integrationParameters: { + /** + * The Azure Media Services Authorization Token. + * + *
- This token will be used for the license request. + */ + token: string; + }; +} diff --git a/drm/src/azuredrm/AzureDrmFairplayContentProtectionIntegration.ts b/drm/src/azuredrm/AzureDrmFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..89501765 --- /dev/null +++ b/drm/src/azuredrm/AzureDrmFairplayContentProtectionIntegration.ts @@ -0,0 +1,62 @@ +import { + BufferSource, + CertificateRequest, + ContentProtectionIntegration, + LicenseRequest, + LicenseResponse, + MaybeAsync +} from 'theoplayer'; +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; +import { isAzureDrmDRMConfiguration } from './AzureDrmUtils'; +import { extractContentId, unwrapCkc } from '../utils/FairplayUtils'; +import { fromStringToUint8Array, fromUint8ArrayToBase64String } from '../utils/TypeUtils'; + +export class AzureDrmFairplayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: AzureDrmConfiguration; + private contentId: string | undefined = undefined; + + constructor(configuration: AzureDrmConfiguration) { + if (!isAzureDrmDRMConfiguration(configuration)) { + throw new Error('The FairPlay AzureDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.certificateURL) { + throw new Error('The FairPlay AzureDRM certificate url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.certificateURL; + request.headers = { + ...request.headers, + Authorization: `Bearer ${this.contentProtectionConfiguration.integrationParameters.token}` + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL) { + throw new Error('The FairPlay AzureDRM license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + Authorization: `Bearer ${this.contentProtectionConfiguration.integrationParameters.token}` + }; + if (!this.contentId) { + throw new Error('The FairPlay AzureDRM content ID has not been correctly configured.'); + } + const licenseParameters = `spc=${encodeURIComponent(fromUint8ArrayToBase64String(request.body!))}&assetId=${encodeURIComponent(this.contentId)}`; + request.body = fromStringToUint8Array(licenseParameters); + return request; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + return unwrapCkc(response.body); + } + + extractFairplayContentId(skdUrl: string): string { + this.contentId = extractContentId(skdUrl); + return this.contentId; + } +} diff --git a/drm/src/azuredrm/AzureDrmFairplayContentProtectionIntegrationFactory.ts b/drm/src/azuredrm/AzureDrmFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..763ae3c7 --- /dev/null +++ b/drm/src/azuredrm/AzureDrmFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; +import { AzureDrmFairplayContentProtectionIntegration } from './AzureDrmFairplayContentProtectionIntegration'; + +export class AzureDrmFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: AzureDrmConfiguration): ContentProtectionIntegration { + return new AzureDrmFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/azuredrm/AzureDrmPlayReadyContentProtectionIntegration.ts b/drm/src/azuredrm/AzureDrmPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..4361fcda --- /dev/null +++ b/drm/src/azuredrm/AzureDrmPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,26 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; +import { isAzureDrmDRMConfiguration } from './AzureDrmUtils'; + +export class AzureDrmPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: AzureDrmConfiguration; + + constructor(configuration: AzureDrmConfiguration) { + if (!isAzureDrmDRMConfiguration(configuration)) { + throw new Error('The PlayReady AzureDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.playready?.licenseAcquisitionURL) { + throw new Error('The PlayReady AzureDRM license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.playready?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + Authorization: `Bearer ${this.contentProtectionConfiguration.integrationParameters.token}` + }; + return request; + } +} diff --git a/drm/src/azuredrm/AzureDrmPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/azuredrm/AzureDrmPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..e5927581 --- /dev/null +++ b/drm/src/azuredrm/AzureDrmPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; +import { AzureDrmPlayReadyContentProtectionIntegration } from './AzureDrmPlayReadyContentProtectionIntegration'; + +export class AzureDrmPlayReadyContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: AzureDrmConfiguration): ContentProtectionIntegration { + return new AzureDrmPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/azuredrm/AzureDrmUtils.ts b/drm/src/azuredrm/AzureDrmUtils.ts new file mode 100644 index 00000000..eb1f8264 --- /dev/null +++ b/drm/src/azuredrm/AzureDrmUtils.ts @@ -0,0 +1,5 @@ +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; + +export function isAzureDrmDRMConfiguration(configuration: AzureDrmConfiguration): boolean { + return configuration.integrationParameters.token !== undefined; +} diff --git a/drm/src/azuredrm/AzureDrmWidevineContentProtectionIntegration.ts b/drm/src/azuredrm/AzureDrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..462b8808 --- /dev/null +++ b/drm/src/azuredrm/AzureDrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,26 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; +import { isAzureDrmDRMConfiguration } from './AzureDrmUtils'; + +export class AzureDrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: AzureDrmConfiguration; + + constructor(configuration: AzureDrmConfiguration) { + if (!isAzureDrmDRMConfiguration(configuration)) { + throw new Error('The Widevine AzureDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL) { + throw new Error('The Widevine AzureDRM license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + Authorization: `Bearer ${this.contentProtectionConfiguration.integrationParameters.token}` + }; + return request; + } +} diff --git a/drm/src/azuredrm/AzureDrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/azuredrm/AzureDrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..6d820ddb --- /dev/null +++ b/drm/src/azuredrm/AzureDrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { AzureDrmConfiguration } from './AzureDrmConfiguration'; +import { AzureDrmWidevineContentProtectionIntegration } from './AzureDrmWidevineContentProtectionIntegration'; + +export class AzureDrmWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: AzureDrmConfiguration): ContentProtectionIntegration { + return new AzureDrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/azuredrm/barrel.ts b/drm/src/azuredrm/barrel.ts new file mode 100644 index 00000000..03e6850b --- /dev/null +++ b/drm/src/azuredrm/barrel.ts @@ -0,0 +1,9 @@ +export * from './AzureDrmConfiguration'; +export * from './AzureDrmUtils'; + +export * from './AzureDrmFairplayContentProtectionIntegration'; +export * from './AzureDrmFairplayContentProtectionIntegrationFactory'; +export * from './AzureDrmPlayReadyContentProtectionIntegration'; +export * from './AzureDrmPlayReadyContentProtectionIntegrationFactory'; +export * from './AzureDrmWidevineContentProtectionIntegration'; +export * from './AzureDrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/castlabs/CastLabsDrmConfiguration.ts b/drm/src/castlabs/CastLabsDrmConfiguration.ts new file mode 100644 index 00000000..8e775816 --- /dev/null +++ b/drm/src/castlabs/CastLabsDrmConfiguration.ts @@ -0,0 +1,43 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Comcast DRM integration. + */ +export type DRMTodayIntegrationID = 'castlabs'; + +/** + * Describes the configuration of the Comcast DRM integration. + */ +export interface CastLabsDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: DRMTodayIntegrationID; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration. + */ + integrationParameters: { + /** + * The Comcast Release Pid. + * + *
- This releasePid will be used for the license request. + */ + merchant: string; + + /** + * The Comcast Account ID. + * + *
- This account will be used for the license request. + */ + sessionId: string; + + /** + * The Comcast Token. + * + *
- This token will be used for the license request. + */ + userId: string; + }; +} diff --git a/drm/src/castlabs/CastLabsDrmFairPlayContentProtectionIntegration.ts b/drm/src/castlabs/CastLabsDrmFairPlayContentProtectionIntegration.ts new file mode 100644 index 00000000..1cfaa4e5 --- /dev/null +++ b/drm/src/castlabs/CastLabsDrmFairPlayContentProtectionIntegration.ts @@ -0,0 +1,55 @@ +import { + BufferSource, + CertificateRequest, + ContentProtectionIntegration, + LicenseRequest, + LicenseResponse, + MaybeAsync +} from 'theoplayer'; +import { CastLabsDrmConfiguration } from './CastLabsDrmConfiguration'; +import { unwrapCkc } from '../utils/FairplayUtils'; +import { fromObjectToBase64String, fromStringToUint8Array, fromUint8ArrayToBase64String } from '../utils/TypeUtils'; + +export class CastLabsDrmFairPlayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: CastLabsDrmConfiguration; + private contentId: string | undefined = undefined; + private generatedToken: string; + + constructor(configuration: CastLabsDrmConfiguration) { + this.contentProtectionConfiguration = configuration; + const jsonObj = { + userId: this.contentProtectionConfiguration.integrationParameters.userId, + sessionId: this.contentProtectionConfiguration.integrationParameters.sessionId, + merchant: this.contentProtectionConfiguration.integrationParameters.merchant + }; + this.generatedToken = fromObjectToBase64String(jsonObj); + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'dt-custom-data': this.generatedToken! + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'content-type': 'application/x-www-form-urlencoded', + 'dt-custom-data': this.generatedToken! + }; + const body = `spc=${encodeURIComponent(fromUint8ArrayToBase64String(request.body!))}&${encodeURIComponent(this.contentId!)}`; + request.body = fromStringToUint8Array(body); + return request; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + return unwrapCkc(response.body); + } + + extractFairplayContentId(skdUrl: string): string { + this.contentId = skdUrl; + return this.contentId; + } +} diff --git a/drm/src/castlabs/CastLabsDrmFairPlayContentProtectionIntegrationFactory.ts b/drm/src/castlabs/CastLabsDrmFairPlayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..2dcb65d8 --- /dev/null +++ b/drm/src/castlabs/CastLabsDrmFairPlayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { CastLabsDrmConfiguration } from './CastLabsDrmConfiguration'; +import { CastLabsDrmFairPlayContentProtectionIntegration } from './CastLabsDrmFairPlayContentProtectionIntegration'; + +export class CastLabsDrmFairPlayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: CastLabsDrmConfiguration): ContentProtectionIntegration { + return new CastLabsDrmFairPlayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/castlabs/CastLabsDrmUtils.ts b/drm/src/castlabs/CastLabsDrmUtils.ts new file mode 100644 index 00000000..51d2034e --- /dev/null +++ b/drm/src/castlabs/CastLabsDrmUtils.ts @@ -0,0 +1,9 @@ +import { CastLabsDrmConfiguration } from './CastLabsDrmConfiguration'; + +export function isCastLabsDrmDRMConfiguration(configuration: CastLabsDrmConfiguration): boolean { + return ( + configuration.integrationParameters.merchant !== undefined && + configuration.integrationParameters.sessionId !== undefined && + configuration.integrationParameters.userId !== undefined + ); +} diff --git a/drm/src/castlabs/barrel.ts b/drm/src/castlabs/barrel.ts new file mode 100644 index 00000000..e87ca4a1 --- /dev/null +++ b/drm/src/castlabs/barrel.ts @@ -0,0 +1,5 @@ +export * from './CastLabsDrmConfiguration'; +export * from './CastLabsDrmUtils'; + +export * from './CastLabsDrmFairPlayContentProtectionIntegration'; +export * from './CastLabsDrmFairPlayContentProtectionIntegrationFactory'; diff --git a/drm/src/comcastdrm/ComcastDrmConfiguration.ts b/drm/src/comcastdrm/ComcastDrmConfiguration.ts new file mode 100644 index 00000000..dc30fb87 --- /dev/null +++ b/drm/src/comcastdrm/ComcastDrmConfiguration.ts @@ -0,0 +1,43 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Comcast DRM integration. + */ +export type ComcastIntegrationID = 'comcast'; + +/** + * Describes the configuration of the Comcast DRM integration. + */ +export interface ComcastDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: ComcastIntegrationID; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration. + */ + integrationParameters: { + /** + * The Comcast Release Pid. + * + *
- This releasePid will be used for the license request. + */ + releasePid: string; + + /** + * The Comcast Account ID. + * + *
- This account will be used for the license request. + */ + account: string; + + /** + * The Comcast Token. + * + *
- This token will be used for the license request. + */ + token: string; + }; +} diff --git a/drm/src/comcastdrm/ComcastDrmFairPlayContentProtectionIntegration.ts b/drm/src/comcastdrm/ComcastDrmFairPlayContentProtectionIntegration.ts new file mode 100644 index 00000000..d113a175 --- /dev/null +++ b/drm/src/comcastdrm/ComcastDrmFairPlayContentProtectionIntegration.ts @@ -0,0 +1,63 @@ +import { BufferSource, ContentProtectionIntegration, LicenseRequest, LicenseResponse, MaybeAsync } from 'theoplayer'; +import { ComcastDrmConfiguration } from './ComcastDrmConfiguration'; +import { isComcastDrmDRMConfiguration } from './ComcastDrmUtils'; +import { extractContentId } from '../utils/FairplayUtils'; +import { + fromBase64StringToArrayBuffer, + fromObjectToUint8Array, + fromUint8ArrayToBase64String, + fromUint8ArrayToObject +} from '../utils/TypeUtils'; + +export class ComcastDrmFairPlayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: ComcastDrmConfiguration; + private contentId: string | undefined = undefined; + + constructor(configuration: ComcastDrmConfiguration) { + if (!isComcastDrmDRMConfiguration(configuration)) { + throw new Error( + 'The FairPlay ComcastDRM configuration is incorrect.' + + 'Please verify that you have configured a token, releasePid and account.' + ); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL) { + throw new Error('The FairPlay ComcastDRM license url has not been correctly configured.'); + } + const { token, account, releasePid } = this.contentProtectionConfiguration.integrationParameters; + const spcMessage = fromUint8ArrayToBase64String(request.body!); + const body = { + getFairplayLicense: { + releasePid: releasePid, + spcMessage: spcMessage + } + }; + + const newBody = fromObjectToUint8Array(body); + return { + ...request, + url: request.url + `&token=${token}&account=${account}&form=json`, + headers: { + 'Content-Type': 'application/json' + }, + body: newBody + }; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + const responseObject = fromUint8ArrayToObject(response.body); + return fromBase64StringToArrayBuffer(responseObject.getFairplayLicenseResponse.ckcResponse); + } + + extractFairplayContentId(skdUrl: string): string { + const modifiedContentId = skdUrl.replace( + 'FairPlay', + this.contentProtectionConfiguration.integrationParameters.releasePid + ); + this.contentId = extractContentId(modifiedContentId); + return this.contentId; + } +} diff --git a/drm/src/comcastdrm/ComcastDrmFairPlayContentProtectionIntegrationFactory.ts b/drm/src/comcastdrm/ComcastDrmFairPlayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..24947c95 --- /dev/null +++ b/drm/src/comcastdrm/ComcastDrmFairPlayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { ComcastDrmConfiguration } from './ComcastDrmConfiguration'; +import { ComcastDrmFairPlayContentProtectionIntegration } from './ComcastDrmFairPlayContentProtectionIntegration'; + +export class ComcastDrmFairPlayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: ComcastDrmConfiguration): ContentProtectionIntegration { + return new ComcastDrmFairPlayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/comcastdrm/ComcastDrmUtils.ts b/drm/src/comcastdrm/ComcastDrmUtils.ts new file mode 100644 index 00000000..4739a9ec --- /dev/null +++ b/drm/src/comcastdrm/ComcastDrmUtils.ts @@ -0,0 +1,9 @@ +import { ComcastDrmConfiguration } from './ComcastDrmConfiguration'; + +export function isComcastDrmDRMConfiguration(configuration: ComcastDrmConfiguration): boolean { + return ( + configuration.integrationParameters.token !== undefined && + configuration.integrationParameters.releasePid !== undefined && + configuration.integrationParameters.account !== undefined + ); +} diff --git a/drm/src/comcastdrm/ComcastDrmWidevineContentProtectionIntegration.ts b/drm/src/comcastdrm/ComcastDrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..c6de68fa --- /dev/null +++ b/drm/src/comcastdrm/ComcastDrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,70 @@ +import { + ContentProtectionIntegration, + LicenseRequest, + MaybeAsync, + BufferSource, + LicenseResponse, + CertificateRequest, + CertificateResponse +} from 'theoplayer'; +import { ComcastDrmConfiguration } from './ComcastDrmConfiguration'; +import { isComcastDrmDRMConfiguration } from './ComcastDrmUtils'; +import { fromObjectToUint8Array, fromUint8ArrayToBase64String, fromUint8ArrayToString } from '../utils/TypeUtils'; + +export class ComcastDrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: ComcastDrmConfiguration; + + constructor(configuration: ComcastDrmConfiguration) { + if (!isComcastDrmDRMConfiguration(configuration)) { + throw new Error('The Widevine AzureDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + const { token, account, releasePid } = this.contentProtectionConfiguration.integrationParameters; + const widevineChallenge = fromUint8ArrayToBase64String(request.body!); + const body = { + getWidevineLicense: { + releasePid: releasePid, + widevineChallenge: widevineChallenge + } + }; + return { + ...request, + url: request.url + `&token=${token}&account=${account}`, + body: fromObjectToUint8Array(body) + }; + } + + onCertificateResponse(response: CertificateResponse): MaybeAsync { + const responseAsText = fromUint8ArrayToString(response.body); + const responseObject = JSON.parse(responseAsText); + return Uint8Array.from(atob(responseObject.getWidevineLicenseResponse.license), (c) => c.charCodeAt(0)).buffer; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL) { + throw new Error('The Widevine AzureDRM license url has not been correctly configured.'); + } + const { token, account, releasePid } = this.contentProtectionConfiguration.integrationParameters; + const widevineChallenge = fromUint8ArrayToBase64String(request.body!); + const body = { + getWidevineLicense: { + releasePid: releasePid, + widevineChallenge: widevineChallenge + } + }; + return { + ...request, + url: request.url + `&token=${token}&account=${account}`, + body: fromObjectToUint8Array(body) + }; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + const responseAsText = fromUint8ArrayToString(response.body); + const responseObject = JSON.parse(responseAsText); + return Uint8Array.from(atob(responseObject.getWidevineLicenseResponse.license), (c) => c.charCodeAt(0)).buffer; + } +} diff --git a/drm/src/comcastdrm/ComcastDrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/comcastdrm/ComcastDrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..43dfd6d5 --- /dev/null +++ b/drm/src/comcastdrm/ComcastDrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { ComcastDrmConfiguration } from './ComcastDrmConfiguration'; +import { ComcastDrmWidevineContentProtectionIntegration } from './ComcastDrmWidevineContentProtectionIntegration'; + +export class ComcastDrmWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: ComcastDrmConfiguration): ContentProtectionIntegration { + return new ComcastDrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/comcastdrm/barrel.ts b/drm/src/comcastdrm/barrel.ts new file mode 100644 index 00000000..8750d736 --- /dev/null +++ b/drm/src/comcastdrm/barrel.ts @@ -0,0 +1,7 @@ +export * from './ComcastDrmConfiguration'; +export * from './ComcastDrmUtils'; + +export * from './ComcastDrmFairPlayContentProtectionIntegration'; +export * from './ComcastDrmFairPlayContentProtectionIntegrationFactory'; +export * from './ComcastDrmWidevineContentProtectionIntegration'; +export * from './ComcastDrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/ezdrm/EzdrmDrmConfiguration.ts b/drm/src/ezdrm/EzdrmDrmConfiguration.ts new file mode 100644 index 00000000..e7a2bc49 --- /dev/null +++ b/drm/src/ezdrm/EzdrmDrmConfiguration.ts @@ -0,0 +1,26 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Ezdrm integration. + */ +export type EzdrmIntegrationID = 'ezdrm'; + +/** + * Describes the configuration of the Ezdrm DRM integration. + * + * ``` + * const drmConfiguration = { + * integration : 'ezdrm', + * fairplay: { + * certificateURL: 'yourEzdrmCertificateUrl', + * licenseAcquisitionURL: 'yourEzdrmLicenseAcquisitionURL' + * } + * } + * ``` + */ +export interface EzdrmDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: EzdrmIntegrationID; +} diff --git a/drm/src/ezdrm/EzdrmFairplayContentProtectionIntegration.ts b/drm/src/ezdrm/EzdrmFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..90516ed3 --- /dev/null +++ b/drm/src/ezdrm/EzdrmFairplayContentProtectionIntegration.ts @@ -0,0 +1,42 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource, CertificateRequest } from 'theoplayer'; +import { EzdrmDrmConfiguration } from './EzdrmDrmConfiguration'; +import { extractContentId } from '../utils/FairplayUtils'; + +export class EzdrmFairplayContentProtectionIntegration implements ContentProtectionIntegration { + static readonly DEFAULT_CERTIFICATE_URL = 'insert default certificate url here'; + static readonly DEFAULT_LICENSE_URL = 'insert default license url here'; + + private readonly contentProtectionConfiguration: EzdrmDrmConfiguration; + private contentId: string | undefined = undefined; + + constructor(configuration: EzdrmDrmConfiguration) { + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.url = + this.contentProtectionConfiguration.fairplay?.certificateURL ?? + EzdrmFairplayContentProtectionIntegration.DEFAULT_CERTIFICATE_URL; + request.headers = { + ...request.headers, + 'Content-Type': 'application/octet-stream' + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.url = + this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL ?? + EzdrmFairplayContentProtectionIntegration.DEFAULT_LICENSE_URL; + request.headers = { + ...request.headers, + 'Content-Type': 'application/octet-stream' + }; + return request; + } + + extractFairplayContentId(skdUrl: string): string { + this.contentId = extractContentId(skdUrl); + return this.contentId; + } +} diff --git a/drm/src/ezdrm/EzdrmFairplayContentProtectionIntegrationFactory.ts b/drm/src/ezdrm/EzdrmFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..e9b14fe0 --- /dev/null +++ b/drm/src/ezdrm/EzdrmFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { EzdrmDrmConfiguration } from './EzdrmDrmConfiguration'; +import { EzdrmFairplayContentProtectionIntegration } from './EzdrmFairplayContentProtectionIntegration'; + +export class EzdrmFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: EzdrmDrmConfiguration): ContentProtectionIntegration { + return new EzdrmFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/ezdrm/barrel.ts b/drm/src/ezdrm/barrel.ts new file mode 100644 index 00000000..6be7935b --- /dev/null +++ b/drm/src/ezdrm/barrel.ts @@ -0,0 +1,4 @@ +export * from './EzdrmDrmConfiguration'; + +export * from './EzdrmFairplayContentProtectionIntegration'; +export * from './EzdrmFairplayContentProtectionIntegrationFactory'; diff --git a/drm/src/index.ts b/drm/src/index.ts new file mode 100644 index 00000000..9f02fa3a --- /dev/null +++ b/drm/src/index.ts @@ -0,0 +1,11 @@ +export * from './axinomdrm/barrel'; +export * from './azuredrm/barrel'; +export * from './castlabs/barrel'; +export * from './comcastdrm/barrel'; +export * from './ezdrm/barrel'; +export * from './irdetocontrol/barrel'; +export * from './keyos/barrel'; +export * from './nagradrm/barrel'; +export * from './titaniumdrm/barrel'; +export * from './verimatrixcoredrm/barrel'; +export * from './vudrm/barrel'; diff --git a/drm/src/irdetocontrol/IrdetoControlConfiguration.ts b/drm/src/irdetocontrol/IrdetoControlConfiguration.ts new file mode 100644 index 00000000..9ef21896 --- /dev/null +++ b/drm/src/irdetocontrol/IrdetoControlConfiguration.ts @@ -0,0 +1,37 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Irdeto Control integration. + */ +export type IrdetoControlIntegrationID = 'irdetocontrol'; + +/** + * Describes the configuration of the Irdeto Control DRM integration. + */ +export interface IrdetoControlConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: IrdetoControlIntegrationID; + + integrationParameters?: { + /** + * The Irdeto Control Content ID. + * + *
- This Content ID will be used for the license request. + */ + contentId?: string; + /** + * The Irdeto Control Key ID. + * + *
- This Key ID will be used for the license request. + */ + keyId?: string; + /** + * The Irdeto Control Application ID. + * + *
- This Application ID will be used for the certificate request. + */ + applicationId?: string; + }; +} diff --git a/drm/src/irdetocontrol/IrdetoControlFairplayContentProtectionIntegration.ts b/drm/src/irdetocontrol/IrdetoControlFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..1e84e285 --- /dev/null +++ b/drm/src/irdetocontrol/IrdetoControlFairplayContentProtectionIntegration.ts @@ -0,0 +1,108 @@ +import { + BufferSource, + ContentProtectionIntegration, + CertificateRequest, + LicenseRequest, + LicenseResponse, + MaybeAsync +} from 'theoplayer'; +import { IrdetoControlConfiguration } from './IrdetoControlConfiguration'; + +export class IrdetoControlFairplayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: IrdetoControlConfiguration; + private contentId: string | undefined = undefined; + private keyId: string | undefined = undefined; + + constructor(configuration: IrdetoControlConfiguration) { + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.certificateURL) { + throw new Error('The FairPlay Irdeto Control certificate url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.certificateURL; + if ( + !this.hasQueryParameter(request.url, 'applicationId') && + this.contentProtectionConfiguration.integrationParameters?.applicationId + ) { + request.url = this.appendQueryParameter( + request.url, + 'applicationId', + this.contentProtectionConfiguration.integrationParameters?.applicationId + ); + } + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL) { + throw new Error('The FairPlay Irdeto DRM license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL; + if (!this.hasQueryParameter(request.url, 'contentId')) { + if (this.contentProtectionConfiguration.integrationParameters?.contentId) { + request.url = this.appendQueryParameter( + request.url, + 'contentId', + this.contentProtectionConfiguration.integrationParameters?.contentId + ); + } else if (this.contentId) { + request.url = this.appendQueryParameter(request.url, 'contentId', this.contentId); + } + } + if (!this.hasQueryParameter(request.url, 'keyId')) { + if (this.contentProtectionConfiguration.integrationParameters?.keyId) { + request.url = this.appendQueryParameter( + request.url, + 'keyId', + this.contentProtectionConfiguration.integrationParameters?.keyId + ); + } else if (this.keyId) { + request.url = this.appendQueryParameter(request.url, 'keyId', this.keyId); + } + } + return request; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + return response.body; + } + + extractFairplayContentId(skdUrl: string): string { + const parameters = skdUrl.split('?')[1].split('&'); + for (let i = 0; i < parameters.length; i++) { + const pair = parameters[i].split('='); + if (pair[0] == 'contentId') { + this.contentId = pair[1]; + } else if (pair[0] == 'keyId') { + this.keyId = pair[1]; + } + } + return skdUrl; + } + + hasQueryParameter(url: string, parameter: string): boolean { + const queryParameters = url.split('?'); + if (!queryParameters || !queryParameters[1]) { + return false; + } + const pairs = queryParameters[1].split('&'); + for (let i = 0; i < pairs.length; i++) { + const pair = pairs[i].split('='); + if (pair[0] == parameter) { + return true; + } + } + return false; + } + + appendQueryParameter(url: string, key: string, value: string | undefined): string { + const queryParameters = url.split('?'); + if (!queryParameters || !queryParameters[1]) { + return url + '?' + key + '=' + value; + } else { + return url + '&' + key + '=' + value; + } + } +} diff --git a/drm/src/irdetocontrol/IrdetoControlFairplayContentProtectionIntegrationFactory.ts b/drm/src/irdetocontrol/IrdetoControlFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..b485a5af --- /dev/null +++ b/drm/src/irdetocontrol/IrdetoControlFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { IrdetoControlConfiguration } from './IrdetoControlConfiguration'; +import { IrdetoControlFairplayContentProtectionIntegration } from './IrdetoControlFairplayContentProtectionIntegration'; + +export class IrdetoControlFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: IrdetoControlConfiguration): ContentProtectionIntegration { + return new IrdetoControlFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/irdetocontrol/barrel.ts b/drm/src/irdetocontrol/barrel.ts new file mode 100644 index 00000000..68a4c946 --- /dev/null +++ b/drm/src/irdetocontrol/barrel.ts @@ -0,0 +1,4 @@ +export * from './IrdetoControlConfiguration'; + +export * from './IrdetoControlFairplayContentProtectionIntegration'; +export * from './IrdetoControlFairplayContentProtectionIntegrationFactory'; diff --git a/drm/src/keyos/KeyOSDrmConfiguration.ts b/drm/src/keyos/KeyOSDrmConfiguration.ts new file mode 100644 index 00000000..59d6e956 --- /dev/null +++ b/drm/src/keyos/KeyOSDrmConfiguration.ts @@ -0,0 +1,29 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the KeyOS BuyDRM integration. + */ +export type KeyOSIntegrationId = 'keyos_buydrm'; + +/** + * Describes the configuration of the KeyOS BuyDRM DRM integration. + */ +export interface KeyOSDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: KeyOSIntegrationId; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration. + */ + integrationParameters: { + /** + * The KeyOS BuyDRM Authorization Token. + * + *
- Token that will be added to the headers of the license request. + */ + 'x-keyos-authorization': string; + }; +} diff --git a/drm/src/keyos/KeyOSDrmFairplayContentProtectionIntegration.ts b/drm/src/keyos/KeyOSDrmFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..92182cbf --- /dev/null +++ b/drm/src/keyos/KeyOSDrmFairplayContentProtectionIntegration.ts @@ -0,0 +1,69 @@ +import { + ContentProtectionIntegration, + LicenseRequest, + MaybeAsync, + BufferSource, + CertificateRequest, + LicenseResponse +} from 'theoplayer'; +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; +import { isKeyOSDrmDRMConfiguration, extractContentId } from './KeyOSDrmUtils'; +import { + fromBase64StringToUint8Array, + fromStringToUint8Array, + fromUint8ArrayToBase64String, + fromUint8ArrayToUtf8String +} from '../utils/TypeUtils'; + +export class KeyOSDrmFairplayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: KeyOSDrmConfiguration; + private contentId: string | undefined = undefined; + + constructor(configuration: KeyOSDrmConfiguration) { + if (!isKeyOSDrmDRMConfiguration(configuration)) { + throw new Error('The KeyOS customdata value has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.certificateURL) { + throw new Error('The KeyOS certificate url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.certificateURL; + request.headers = { + ...request.headers, + 'x-keyos-authorization': this.contentProtectionConfiguration.integrationParameters['x-keyos-authorization'] + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL) { + throw new Error('The KeyOS Fairplay license url has not been correctly configured.'); + } + + request.url = this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'x-keyos-authorization': this.contentProtectionConfiguration.integrationParameters['x-keyos-authorization'] + }; + const licenseParameters = `spc=${fromUint8ArrayToBase64String(request.body!)}&assetId=${this.contentId}`; + request.body = fromStringToUint8Array(licenseParameters); + return request; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + const bodyAsString = fromUint8ArrayToUtf8String(response.body); + let keyText = bodyAsString.trim(); + if (keyText.substr(0, 5) === '' && keyText.substr(-6) === '') { + keyText = keyText.slice(5, -6); + } + return fromBase64StringToUint8Array(keyText); + } + + extractFairplayContentId(skdUrl: string): string { + this.contentId = extractContentId(skdUrl); + return this.contentId; + } +} diff --git a/drm/src/keyos/KeyOSDrmFairplayContentProtectionIntegrationFactory.ts b/drm/src/keyos/KeyOSDrmFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..92d0bc53 --- /dev/null +++ b/drm/src/keyos/KeyOSDrmFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; +import { KeyOSDrmFairplayContentProtectionIntegration } from './KeyOSDrmFairplayContentProtectionIntegration'; + +export class KeyOSDrmFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: KeyOSDrmConfiguration): ContentProtectionIntegration { + return new KeyOSDrmFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/keyos/KeyOSDrmPlayReadyContentProtectionIntegration.ts b/drm/src/keyos/KeyOSDrmPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..bfd2fbdc --- /dev/null +++ b/drm/src/keyos/KeyOSDrmPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,26 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; +import { isKeyOSDrmDRMConfiguration } from './KeyOSDrmUtils'; + +export class KeyOSDrmPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: KeyOSDrmConfiguration; + + constructor(configuration: KeyOSDrmConfiguration) { + if (!isKeyOSDrmDRMConfiguration(configuration)) { + throw new Error('The KeyOS customdata value has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.playready?.licenseAcquisitionURL) { + throw new Error('The PlayReady KeyOS license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.playready?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'x-keyos-authorization': this.contentProtectionConfiguration.integrationParameters['x-keyos-authorization'] + }; + return request; + } +} diff --git a/drm/src/keyos/KeyOSDrmPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/keyos/KeyOSDrmPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..20a33177 --- /dev/null +++ b/drm/src/keyos/KeyOSDrmPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; +import { KeyOSDrmPlayReadyContentProtectionIntegration } from './KeyOSDrmPlayReadyContentProtectionIntegration'; + +export class KeyOSDrmPlayReadyContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: KeyOSDrmConfiguration): ContentProtectionIntegration { + return new KeyOSDrmPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/keyos/KeyOSDrmUtils.ts b/drm/src/keyos/KeyOSDrmUtils.ts new file mode 100644 index 00000000..f1138182 --- /dev/null +++ b/drm/src/keyos/KeyOSDrmUtils.ts @@ -0,0 +1,14 @@ +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; + +export function isKeyOSDrmDRMConfiguration(configuration: KeyOSDrmConfiguration): boolean { + return configuration.integrationParameters['x-keyos-authorization'] !== undefined; +} + +export function extractContentId(skdUrl: string): string { + if (skdUrl.indexOf('skd') > 0 || skdUrl.indexOf('http') > 0) { + skdUrl = skdUrl.substring(1); + } + const link = document.createElement('a'); + link.href = skdUrl; + return link.hostname; +} diff --git a/drm/src/keyos/KeyOSDrmWidevineContentProtectionIntegration.ts b/drm/src/keyos/KeyOSDrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..6acb01d6 --- /dev/null +++ b/drm/src/keyos/KeyOSDrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,26 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; +import { isKeyOSDrmDRMConfiguration } from './KeyOSDrmUtils'; + +export class KeyOSDrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: KeyOSDrmConfiguration; + + constructor(configuration: KeyOSDrmConfiguration) { + if (!isKeyOSDrmDRMConfiguration(configuration)) { + throw new Error('The KeyOS token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL) { + throw new Error('The Widevine KeyOS license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'x-keyos-authorization': this.contentProtectionConfiguration.integrationParameters['x-keyos-authorization'] + }; + return request; + } +} diff --git a/drm/src/keyos/KeyOSDrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/keyos/KeyOSDrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..121c3f93 --- /dev/null +++ b/drm/src/keyos/KeyOSDrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { KeyOSDrmConfiguration } from './KeyOSDrmConfiguration'; +import { KeyOSDrmWidevineContentProtectionIntegration } from './KeyOSDrmWidevineContentProtectionIntegration'; + +export class KeyOSDrmWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: KeyOSDrmConfiguration): ContentProtectionIntegration { + return new KeyOSDrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/keyos/barrel.ts b/drm/src/keyos/barrel.ts new file mode 100644 index 00000000..9456f625 --- /dev/null +++ b/drm/src/keyos/barrel.ts @@ -0,0 +1,9 @@ +export * from './KeyOSDrmConfiguration'; +export * from './KeyOSDrmUtils'; + +export * from './KeyOSDrmFairplayContentProtectionIntegration'; +export * from './KeyOSDrmFairplayContentProtectionIntegrationFactory'; +export * from './KeyOSDrmPlayReadyContentProtectionIntegration'; +export * from './KeyOSDrmPlayReadyContentProtectionIntegrationFactory'; +export * from './KeyOSDrmWidevineContentProtectionIntegration'; +export * from './KeyOSDrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/nagradrm/NagraDrmConfiguration.ts b/drm/src/nagradrm/NagraDrmConfiguration.ts new file mode 100644 index 00000000..20714ca5 --- /dev/null +++ b/drm/src/nagradrm/NagraDrmConfiguration.ts @@ -0,0 +1,30 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Nagra integration. + */ +export type NagraIntegrationId = 'nagra'; + +/** + * Describes the configuration of the Axinom DRM integration. + */ +export interface NagraDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: NagraIntegrationId; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration. + */ + integrationParameters: { + /** + * The Axinom Authorization Token. + * + *
- Token that will be added to the headers of the license request. + */ + token: string; + tenantId: string; + }; +} diff --git a/drm/src/nagradrm/NagraDrmFairPlayContentProtectionIntegration.ts b/drm/src/nagradrm/NagraDrmFairPlayContentProtectionIntegration.ts new file mode 100644 index 00000000..1964d3e9 --- /dev/null +++ b/drm/src/nagradrm/NagraDrmFairPlayContentProtectionIntegration.ts @@ -0,0 +1,60 @@ +import { + ContentProtectionIntegration, + LicenseRequest, + LicenseResponse, + MaybeAsync, + BufferSource, + CertificateRequest +} from 'theoplayer'; +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; +import { isNagraDrmDRMConfiguration } from './NagraDrmUtils'; +import { fromBase64StringToArrayBuffer, fromBase64StringToString, fromUint8ArrayToObject } from '../utils/TypeUtils'; + +export class NagraDrmFairPlayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: NagraDrmConfiguration; + private contentId: string | undefined = undefined; + + constructor(configuration: NagraDrmConfiguration) { + if (!isNagraDrmDRMConfiguration(configuration)) { + throw new Error('The Nagra token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.certificateURL) { + throw new Error('The Nagra certificate url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.certificateURL; + request.headers = { + ...request.headers, + 'nv-authorizations': this.contentProtectionConfiguration.integrationParameters.token, + 'content-type': 'application/octet-stream' + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL) { + throw new Error('The Nagra Fairplay license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'nv-authorizations': this.contentProtectionConfiguration.integrationParameters.token, + 'nv-tenant-id': this.contentProtectionConfiguration.integrationParameters.tenantId, + 'content-type': 'application/octet-stream' + }; + return request; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + const responseObject = fromUint8ArrayToObject(response.body); + return fromBase64StringToArrayBuffer(responseObject.CkcMessage); + } + + extractFairplayContentId(skdUrl: string): string { + this.contentId = fromBase64StringToString(skdUrl.split('skd://')[1].split('?')[0]); + return this.contentId; + } +} diff --git a/drm/src/nagradrm/NagraDrmFairPlayContentProtectionIntegrationFactory.ts b/drm/src/nagradrm/NagraDrmFairPlayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..362f4a1e --- /dev/null +++ b/drm/src/nagradrm/NagraDrmFairPlayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; +import { NagraDrmFairPlayContentProtectionIntegration } from './NagraDrmFairPlayContentProtectionIntegration'; + +export class NagraDrmFairPlayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: NagraDrmConfiguration): ContentProtectionIntegration { + return new NagraDrmFairPlayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/nagradrm/NagraDrmPlayReadyContentProtectionIntegration.ts b/drm/src/nagradrm/NagraDrmPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..68fa1d5e --- /dev/null +++ b/drm/src/nagradrm/NagraDrmPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,35 @@ +import { ContentProtectionIntegration, LicenseRequest, CertificateRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; +import { isNagraDrmDRMConfiguration } from './NagraDrmUtils'; + +export class NagraDrmPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: NagraDrmConfiguration; + + constructor(configuration: NagraDrmConfiguration) { + if (!isNagraDrmDRMConfiguration(configuration)) { + throw new Error('The Nagra token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'nv-authorizations': this.contentProtectionConfiguration.integrationParameters.token, + 'Content-Type': 'application/octet-stream' + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.playready?.licenseAcquisitionURL) { + throw new Error('The PlayReady Nagra license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.playready?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'X-AxDRM-Message': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } +} diff --git a/drm/src/nagradrm/NagraDrmPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/nagradrm/NagraDrmPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..610cb408 --- /dev/null +++ b/drm/src/nagradrm/NagraDrmPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; +import { NagraDrmPlayReadyContentProtectionIntegration } from './NagraDrmPlayReadyContentProtectionIntegration'; + +export class NagraDrmPlayReadyContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: NagraDrmConfiguration): ContentProtectionIntegration { + return new NagraDrmPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/nagradrm/NagraDrmUtils.ts b/drm/src/nagradrm/NagraDrmUtils.ts new file mode 100644 index 00000000..fa6c7f9a --- /dev/null +++ b/drm/src/nagradrm/NagraDrmUtils.ts @@ -0,0 +1,5 @@ +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; + +export function isNagraDrmDRMConfiguration(configuration: NagraDrmConfiguration): boolean { + return configuration.integrationParameters.token !== undefined; +} diff --git a/drm/src/nagradrm/NagraDrmWidevineContentProtectionIntegration.ts b/drm/src/nagradrm/NagraDrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..26c0d949 --- /dev/null +++ b/drm/src/nagradrm/NagraDrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,35 @@ +import { ContentProtectionIntegration, LicenseRequest, CertificateRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; +import { isNagraDrmDRMConfiguration } from './NagraDrmUtils'; + +export class NagraDrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: NagraDrmConfiguration; + + constructor(configuration: NagraDrmConfiguration) { + if (!isNagraDrmDRMConfiguration(configuration)) { + throw new Error('The Nagra token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'nv-authorizations': this.contentProtectionConfiguration.integrationParameters.token, + 'Content-Type': 'application/octet-stream' + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + if (!this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL) { + throw new Error('The Widevine Nagra license url has not been correctly configured.'); + } + request.url = this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL; + request.headers = { + ...request.headers, + 'nv-authorizations': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } +} diff --git a/drm/src/nagradrm/NagraDrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/nagradrm/NagraDrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..1b13eee8 --- /dev/null +++ b/drm/src/nagradrm/NagraDrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { NagraDrmConfiguration } from './NagraDrmConfiguration'; +import { NagraDrmWidevineContentProtectionIntegration } from './NagraDrmWidevineContentProtectionIntegration'; + +export class NagraDrmWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: NagraDrmConfiguration): ContentProtectionIntegration { + return new NagraDrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/nagradrm/barrel.ts b/drm/src/nagradrm/barrel.ts new file mode 100644 index 00000000..56ac4b35 --- /dev/null +++ b/drm/src/nagradrm/barrel.ts @@ -0,0 +1,9 @@ +export * from './NagraDrmConfiguration'; +export * from './NagraDrmUtils'; + +export * from './NagraDrmFairPlayContentProtectionIntegration'; +export * from './NagraDrmFairPlayContentProtectionIntegrationFactory'; +export * from './NagraDrmPlayReadyContentProtectionIntegration'; +export * from './NagraDrmPlayReadyContentProtectionIntegrationFactory'; +export * from './NagraDrmWidevineContentProtectionIntegration'; +export * from './NagraDrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/titaniumdrm/TitaniumBaseRegistration.ts b/drm/src/titaniumdrm/TitaniumBaseRegistration.ts new file mode 100644 index 00000000..feda3db2 --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumBaseRegistration.ts @@ -0,0 +1,165 @@ +import { isDeviceBasedTitaniumDRMConfiguration, isTokenBasedTitaniumDRMConfiguration } from './TitaniumUtils'; +import type { DeviceBasedTitaniumIntegrationParameters } from './TitaniumIntegrationParameters'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; +import type { ContentProtectionError } from 'theoplayer'; +import { ErrorCode } from 'theoplayer'; +import { fromObjectToBase64String } from '../utils/TypeUtils'; + +export interface TitaniumDeviceAuthorizationData { + LatensRegistration: TitaniumLatensRegistration; +} + +export interface TitaniumLatensRegistration { + CustomerName: string; + AccountName: string; + PortalId: string; + FriendlyName: string; + AppVersion: string | undefined; + DeviceInfo: TitaniumDeviceInfo; +} + +export interface TitaniumDeviceInfo extends TitaniumCDMDescription { + FormatVersion: string; + DeviceType: string; + OSType: string; + OSVersion: string; + DeviceVendor: string; + DeviceModel: string; +} + +export interface TitaniumCDMDescription { + DRMProvider: string; + DRMVersion: string; + DRMType: string; +} + +export enum TitaniumCDMType { + WIDEVINE = 'Widevine', + PLAYREADY = 'PlayreadyV2', + PLAYREADY_v2 = TitaniumCDMType.PLAYREADY, + PLAYREADY_v3 = 'PlayreadyV3', + FAIRPLAY = 'Fairplay' +} + +export const TITANIUM_CDM_DESCRIPTIONS: { [cdmType: string /* TitaniumCDMType_*/]: TitaniumCDMDescription } = { + Widevine: { + DRMProvider: 'Google', + DRMVersion: '1.4.8.86', + DRMType: 'Widevine' + }, + PlayreadyV2: { + DRMProvider: 'Microsoft', + DRMVersion: '2.9', + DRMType: 'Playready' + }, + PlayreadyV3: { + DRMProvider: 'Microsoft', + DRMVersion: '3', + DRMType: 'Playready' + }, + Fairplay: { + DRMProvider: 'Apple', + DRMType: 'FairPlay', + DRMVersion: '1.0' + } +}; + +export function getTitaniumDeviceAuthorizationData( + integrationParameters: DeviceBasedTitaniumIntegrationParameters, + cdmType: TitaniumCDMType +): TitaniumDeviceAuthorizationData { + const cdmDescription: TitaniumCDMDescription = TITANIUM_CDM_DESCRIPTIONS[cdmType]; + const accountName = integrationParameters.accountName; + const customerName = integrationParameters.customerName; + const portalId = integrationParameters.portalId; + const friendlyName = integrationParameters.friendlyName; + + return { + LatensRegistration: { + CustomerName: customerName, + AccountName: accountName, + PortalId: portalId, + FriendlyName: friendlyName, + AppVersion: '1.0', + DeviceInfo: { + FormatVersion: '1', + DeviceType: 'PC', + OSType: navigator.platform, + OSVersion: '', + DRMProvider: cdmDescription.DRMProvider, + DRMVersion: cdmDescription.DRMVersion, + DRMType: cdmDescription.DRMType, + DeviceVendor: navigator.vendor, + DeviceModel: navigator.vendorSub + } + } + }; +} + +export function createErrorForMalformedDeviceInfoConfiguration( + configuration: TitaniumDrmConfiguration, + cdmType: TitaniumCDMType +): ContentProtectionError { + let message = `Invalid Titanium ${cdmType} DRM configuration.`; + if (!configuration.integrationParameters.accountName) { + message = `Invalid Titanium ${cdmType} DRM configuration, accountName is not set.`; + } else if (!configuration.integrationParameters.customerName) { + message = `Invalid Titanium ${cdmType} DRM configuration, customerName is not set.`; + } else if (!configuration.integrationParameters.portalId) { + message = `Invalid Titanium ${cdmType} DRM configuration, portalId is not set.`; + } + throw { + code: ErrorCode.CONTENT_PROTECTION_CONFIGURATION_INVALID, + message: message + } as ContentProtectionError; +} + +export function createErrorForMalformedTokenConfiguration( + configuration: TitaniumDrmConfiguration, + cdmType: TitaniumCDMType +): ContentProtectionError { + let message = `Invalid Titanium ${cdmType} DRM configuration.`; + if (!configuration.integrationParameters.authToken) { + message = `Invalid Titanium ${cdmType} DRM configuration, authToken is not set.`; + } + throw { + code: ErrorCode.CONTENT_PROTECTION_CONFIGURATION_INVALID, + message: message + } as ContentProtectionError; +} + +export function createTitaniumAuthHeader(configuration: TitaniumDrmConfiguration, cdmType: TitaniumCDMType): string { + if (isTokenBasedTitaniumDRMConfiguration(configuration.integrationParameters)) { + return `Bearer ${configuration.integrationParameters.authToken}`; + } else { + throw createErrorForMalformedTokenConfiguration(configuration, cdmType); + } +} + +export function createTitaniumDeviceHeader(configuration: TitaniumDrmConfiguration, cdmType: TitaniumCDMType): string { + if (isDeviceBasedTitaniumDRMConfiguration(configuration.integrationParameters)) { + const conf = configuration.integrationParameters as DeviceBasedTitaniumIntegrationParameters; + const deviceAuthorizationData = getTitaniumDeviceAuthorizationData(conf, cdmType); + return fromObjectToBase64String(deviceAuthorizationData); + } else { + throw createErrorForMalformedDeviceInfoConfiguration(configuration, cdmType); + } +} + +export function createTitaniumHeaders( + configuration: TitaniumDrmConfiguration, + cdmType: TitaniumCDMType +): { [key: string]: string } { + const hasAuthToken = isTokenBasedTitaniumDRMConfiguration(configuration.integrationParameters); + if (hasAuthToken) { + return { + 'content-type': 'application/octet-stream', + Authorization: createTitaniumAuthHeader(configuration, cdmType) + }; + } else { + return { + 'content-type': 'application/octet-stream', + 'X-TITANIUM-DRM-CDATA': createTitaniumDeviceHeader(configuration, cdmType) + }; + } +} diff --git a/drm/src/titaniumdrm/TitaniumDrmConfiguration.ts b/drm/src/titaniumdrm/TitaniumDrmConfiguration.ts new file mode 100644 index 00000000..bff0d3fc --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumDrmConfiguration.ts @@ -0,0 +1,10 @@ +import type { DRMConfiguration } from 'theoplayer'; +import type { TitaniumIntegrationParameters } from './TitaniumIntegrationParameters'; + +export type TitaniumIntegrationID = 'titaniumdrm'; + +export interface TitaniumDrmConfiguration extends DRMConfiguration { + integration: TitaniumIntegrationID; + + integrationParameters: TitaniumIntegrationParameters; +} diff --git a/drm/src/titaniumdrm/TitaniumFairplayContentProtectionIntegration.ts b/drm/src/titaniumdrm/TitaniumFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..a523c99a --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumFairplayContentProtectionIntegration.ts @@ -0,0 +1,28 @@ +import { isTitaniumDRMConfiguration } from './TitaniumUtils'; +import { createTitaniumHeaders, TitaniumCDMType } from './TitaniumBaseRegistration'; +import type { ContentProtectionIntegration, LicenseRequest, MaybeAsync } from 'theoplayer'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; + +export class TitaniumFairplayContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: TitaniumDrmConfiguration; + + constructor(drmConfiguration: TitaniumDrmConfiguration) { + if (!isTitaniumDRMConfiguration(drmConfiguration)) { + throw new Error('Titanium DRM has not been correctly configured.'); + } + this.contentProtectionConfiguration = drmConfiguration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + ...createTitaniumHeaders(this.contentProtectionConfiguration, TitaniumCDMType.FAIRPLAY) + }; + return request; + } + + extractFairplayContentId(skdUrl: string): MaybeAsync { + // drop the 'skd://' part + return skdUrl.substring(6, skdUrl.length); + } +} diff --git a/drm/src/titaniumdrm/TitaniumFairplayContentProtectionIntegrationFactory.ts b/drm/src/titaniumdrm/TitaniumFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..ebe893cb --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { TitaniumFairplayContentProtectionIntegration } from './TitaniumFairplayContentProtectionIntegration'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; + +export class TitaniumFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: TitaniumDrmConfiguration): ContentProtectionIntegration { + return new TitaniumFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/titaniumdrm/TitaniumIntegrationParameters.ts b/drm/src/titaniumdrm/TitaniumIntegrationParameters.ts new file mode 100644 index 00000000..f27b9be5 --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumIntegrationParameters.ts @@ -0,0 +1,107 @@ +export interface TitaniumIntegrationParameters { + /** + * The account name. + * + * @remarks + *
- Required when doing device-based authentication. + */ + accountName?: string; + + /** + * The customer name. + * + * @remarks + *
- Required when doing device-based authentication. + */ + customerName?: string; + + /** + * The friendly name of the customer. + * + * @remarks + *
- Required when doing device-based authentication. + */ + friendlyName?: string; + + /** + * The identifier of the portal. + * + * @remarks + *
- Required when doing device-based authentication. + */ + portalId?: string; + + /** + * The authentication token. + * + * @remarks + *
- This is a JSON web token provided by the Titanium Secure Token Server. + *
- Required when doing token-based authentication. + */ + authToken?: string; +} + +export interface DeviceBasedTitaniumIntegrationParameters extends TitaniumIntegrationParameters { + /** + * The account name. + */ + accountName: string; + + /** + * The customer name. + */ + customerName: string; + + /** + * The friendly name of this customer. + */ + friendlyName: string; + + /** + * The identifier of the portal. + */ + portalId: string; + + /** + * The authentication token. + * + * @remarks + *
- This is a JSON web token provided by the Titanium Secure Token Server. + */ + authToken?: undefined; +} + +/** + * Describes the configuration of the Titanium DRM integration with token-based authentication. + * + * @public + */ +export interface TokenBasedTitaniumIntegrationParameters extends TitaniumIntegrationParameters { + /** + * The account name. + */ + accountName?: undefined; + + /** + * The customer name. + */ + customerName?: undefined; + + /** + * The friendly name of this customer. + */ + friendlyName?: undefined; + + /** + * The identifier of the portal. + */ + portalId?: undefined; + + /** + * The authentication token. + * + * @remarks + *
- This is a JSON web token provided by the Titanium Secure Token Server. + */ + authToken: string; +} diff --git a/drm/src/titaniumdrm/TitaniumPlayReadyContentProtectionIntegration.ts b/drm/src/titaniumdrm/TitaniumPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..d3f43acf --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,32 @@ +import { isTitaniumDRMConfiguration } from './TitaniumUtils'; +import { createTitaniumHeaders, TitaniumCDMType } from './TitaniumBaseRegistration'; +import type { BufferSource, ContentProtectionIntegration, LicenseRequest, MaybeAsync } from 'theoplayer'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; +import { CertificateRequest } from 'theoplayer'; + +export class TitaniumPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: TitaniumDrmConfiguration; + + constructor(drmConfiguration: TitaniumDrmConfiguration) { + if (!isTitaniumDRMConfiguration(drmConfiguration)) { + throw new Error('Titanium DRM has not been correctly configured.'); + } + this.contentProtectionConfiguration = drmConfiguration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + ...createTitaniumHeaders(this.contentProtectionConfiguration, TitaniumCDMType.WIDEVINE) + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + ...createTitaniumHeaders(this.contentProtectionConfiguration, TitaniumCDMType.WIDEVINE) + }; + return request; + } +} diff --git a/drm/src/titaniumdrm/TitaniumPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/titaniumdrm/TitaniumPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..8454c8d3 --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; +import { TitaniumPlayReadyContentProtectionIntegration } from './TitaniumPlayReadyContentProtectionIntegration'; + +export class TitaniumPlayReadyContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: TitaniumDrmConfiguration): ContentProtectionIntegration { + return new TitaniumPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/titaniumdrm/TitaniumUtils.ts b/drm/src/titaniumdrm/TitaniumUtils.ts new file mode 100644 index 00000000..be994fd3 --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumUtils.ts @@ -0,0 +1,30 @@ +import type { + DeviceBasedTitaniumIntegrationParameters, + TitaniumIntegrationParameters, + TokenBasedTitaniumIntegrationParameters +} from './TitaniumIntegrationParameters'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; + +export function isTitaniumDRMConfiguration(configuration: TitaniumDrmConfiguration): boolean { + const integrationParameters = configuration.integrationParameters; + return ( + isTokenBasedTitaniumDRMConfiguration(integrationParameters) || + isDeviceBasedTitaniumDRMConfiguration(integrationParameters) + ); +} + +export function isTokenBasedTitaniumDRMConfiguration( + integrationParameters: TitaniumIntegrationParameters +): integrationParameters is TokenBasedTitaniumIntegrationParameters { + return integrationParameters.authToken !== undefined; +} + +export function isDeviceBasedTitaniumDRMConfiguration( + integrationParameters: TitaniumIntegrationParameters +): integrationParameters is DeviceBasedTitaniumIntegrationParameters { + return ( + integrationParameters.accountName !== undefined && + integrationParameters.customerName !== undefined && + integrationParameters.portalId !== undefined + ); +} diff --git a/drm/src/titaniumdrm/TitaniumWidevineContentProtectionIntegration.ts b/drm/src/titaniumdrm/TitaniumWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..9c90d317 --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumWidevineContentProtectionIntegration.ts @@ -0,0 +1,32 @@ +import { isTitaniumDRMConfiguration } from './TitaniumUtils'; +import { createTitaniumHeaders, TitaniumCDMType } from './TitaniumBaseRegistration'; +import type { BufferSource, ContentProtectionIntegration, LicenseRequest, MaybeAsync } from 'theoplayer'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; +import type { CertificateRequest } from 'theoplayer'; + +export class TitaniumWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: TitaniumDrmConfiguration; + + constructor(drmConfiguration: TitaniumDrmConfiguration) { + if (!isTitaniumDRMConfiguration(drmConfiguration)) { + throw new Error('Titanium DRM has not been correctly configured.'); + } + this.contentProtectionConfiguration = drmConfiguration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + ...createTitaniumHeaders(this.contentProtectionConfiguration, TitaniumCDMType.WIDEVINE) + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + ...createTitaniumHeaders(this.contentProtectionConfiguration, TitaniumCDMType.WIDEVINE) + }; + return request; + } +} diff --git a/drm/src/titaniumdrm/TitaniumWidevineContentProtectionIntegrationFactory.ts b/drm/src/titaniumdrm/TitaniumWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..41f1275d --- /dev/null +++ b/drm/src/titaniumdrm/TitaniumWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import type { TitaniumDrmConfiguration } from './TitaniumDrmConfiguration'; +import { TitaniumWidevineContentProtectionIntegration } from './TitaniumWidevineContentProtectionIntegration'; + +export class TitaniumWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: TitaniumDrmConfiguration): ContentProtectionIntegration { + return new TitaniumWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/titaniumdrm/barrel.ts b/drm/src/titaniumdrm/barrel.ts new file mode 100644 index 00000000..140c664f --- /dev/null +++ b/drm/src/titaniumdrm/barrel.ts @@ -0,0 +1,11 @@ +export * from './TitaniumBaseRegistration'; +export * from './TitaniumDrmConfiguration'; +export * from './TitaniumIntegrationParameters'; +export * from './TitaniumUtils'; + +export * from './TitaniumFairplayContentProtectionIntegration'; +export * from './TitaniumFairplayContentProtectionIntegrationFactory'; +export * from './TitaniumPlayReadyContentProtectionIntegration'; +export * from './TitaniumPlayReadyContentProtectionIntegrationFactory'; +export * from './TitaniumWidevineContentProtectionIntegration'; +export * from './TitaniumWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/utils/FairplayUtils.ts b/drm/src/utils/FairplayUtils.ts new file mode 100644 index 00000000..78d4fa3f --- /dev/null +++ b/drm/src/utils/FairplayUtils.ts @@ -0,0 +1,26 @@ +import { fromBase64StringToUint8Array, fromUint8ArrayToString } from './TypeUtils'; + +export function extractContentId(skdUrl: string): string { + const questionMarkIndex = skdUrl.indexOf('?'); + if (questionMarkIndex >= 0) { + return skdUrl.substr(questionMarkIndex + 1); + } else { + const strippedSkd = skdUrl.split('skd://').pop(); + if (strippedSkd) { + const base64regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; + if (base64regex.test(strippedSkd)) { + return strippedSkd; + } + } + const chunks = skdUrl.split('/'); + return chunks[chunks.length - 1]; + } +} + +export function unwrapCkc(data: Uint8Array): Uint8Array { + let license = fromUint8ArrayToString(data).trim(); + if ('' === license.substr(0, 5) && '' === license.substr(-6)) { + license = license.slice(5, -6); + } + return fromBase64StringToUint8Array(license); +} diff --git a/drm/src/utils/TypeUtils.ts b/drm/src/utils/TypeUtils.ts new file mode 100644 index 00000000..08c84f27 --- /dev/null +++ b/drm/src/utils/TypeUtils.ts @@ -0,0 +1,49 @@ +import { utils } from 'theoplayer'; + +export function fromObjectToUint8Array(obj: { [key: string]: any }): Uint8Array { + return new TextEncoder().encode(JSON.stringify(obj)); +} + +export function fromObjectToBase64String(obj: { [key: string]: any }): string { + return fromStringToBase64String(JSON.stringify(obj)); +} + +export function fromStringToUint8Array(str: string): Uint8Array { + return new TextEncoder().encode(str); +} + +export function fromStringToBase64String(str: string): string { + return btoa(str); +} + +export function fromBase64StringToString(str: string): string { + return atob(str); +} + +export function fromBase64StringToArrayBuffer(str: string): ArrayBuffer { + return Uint8Array.from(fromBase64StringToString(str), (c) => c.charCodeAt(0)).buffer; +} + +export function fromUint8ArrayToNumberArray(array: Uint8Array): number[] { + return Array.from(new Uint8Array(array)); +} + +export function fromUint8ArrayToBase64String(array: Uint8Array): string { + return utils.base64.encode(new Uint8Array(array)); +} + +export function fromBase64StringToUint8Array(str: string): Uint8Array { + return utils.base64.decode(str); +} + +export function fromUint8ArrayToString(array: Uint8Array): string { + return new TextDecoder().decode(array); +} + +export function fromUint8ArrayToObject(array: Uint8Array): { [key: string]: any } { + return JSON.parse(new TextDecoder().decode(array)); +} + +export function fromUint8ArrayToUtf8String(array: Uint8Array): string { + return new TextDecoder('utf-8').decode(array); +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmConfiguration.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmConfiguration.ts new file mode 100644 index 00000000..a4fa3b6f --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmConfiguration.ts @@ -0,0 +1,29 @@ +import type { DRMConfiguration } from 'theoplayer'; +import type { VerimatrixCoreIntegrationParameters } from './VerimatrixCoreIntegrationParameters'; + +/** + * The identifier of the Ezdrm integration. + */ +export type VerimatrixCoreDrmIntegrationID = 'verimatrixcoredrmCustom'; + +/** + * Describes the configuration of the Verimatrix Core DRM integration. + * + * ``` + * const drmConfiguration = { + * integration : 'verimatrixcoredrmCustom', + * fairplay: { + * certificateURL: 'yourCertificateUrl', + * licenseAcquisitionURL: 'yourLicenseAcquisitionURL' + * } + * } + * ``` + */ +export interface VerimatrixCoreDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: VerimatrixCoreDrmIntegrationID; + + integrationParameters: VerimatrixCoreIntegrationParameters; +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmFairplayContentProtectionIntegration.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..133ddbb4 --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmFairplayContentProtectionIntegration.ts @@ -0,0 +1,48 @@ +import type { ContentProtectionIntegration, LicenseRequest, LicenseResponse, MaybeAsync } from 'theoplayer'; +import type { VerimatrixCoreDrmConfiguration } from './VerimatrixCoreDrmConfiguration'; +import { + fromBase64StringToUint8Array, + fromObjectToUint8Array, + fromUint8ArrayToBase64String, + fromUint8ArrayToObject +} from '../utils/TypeUtils'; + +export class VerimatrixCoreDrmFairplayContentProtectionIntegration implements ContentProtectionIntegration { + static readonly DEFAULT_LICENSE_URL = 'insert default license url here'; + + private readonly contentProtectionConfiguration: VerimatrixCoreDrmConfiguration; + + constructor(configuration: VerimatrixCoreDrmConfiguration) { + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + const spcMessage = fromUint8ArrayToBase64String(request.body!); + const bodyObject = { + spc: spcMessage + }; + const bodyData = fromObjectToUint8Array(bodyObject); + request.url = + this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL ?? + VerimatrixCoreDrmFairplayContentProtectionIntegration.DEFAULT_LICENSE_URL; + request.headers = { + 'content-type': 'application/json', + Authorization: this.contentProtectionConfiguration.integrationParameters.drmToken ?? '' + }; + request.body = bodyData; + return request; + } + + onLicenseResponse?(response: LicenseResponse): MaybeAsync { + const responseObject = fromUint8ArrayToObject(response.body); + return fromBase64StringToUint8Array(responseObject.ckc); + } + + extractFairplayContentId(skdUrl: string): string { + // drop params in url + const chunks = skdUrl.split('?'); + const sdkUrlWithoutParams = chunks[0]; + // drop the 'skd://' part + return sdkUrlWithoutParams.substring(6, sdkUrlWithoutParams.length); + } +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmFairplayContentProtectionIntegrationFactory.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..43a13b39 --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,11 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import type { VerimatrixCoreDrmConfiguration } from './VerimatrixCoreDrmConfiguration'; +import { VerimatrixCoreDrmFairplayContentProtectionIntegration } from './VerimatrixCoreDrmFairplayContentProtectionIntegration'; + +export class VerimatrixCoreDrmFairplayContentProtectionIntegrationFactory + implements ContentProtectionIntegrationFactory +{ + build(configuration: VerimatrixCoreDrmConfiguration): ContentProtectionIntegration { + return new VerimatrixCoreDrmFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmPlayReadyContentProtectionIntegration.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..f9d67be6 --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,19 @@ +import type { ContentProtectionIntegration, LicenseRequest, MaybeAsync } from 'theoplayer'; +import type { VerimatrixCoreDrmConfiguration } from './VerimatrixCoreDrmConfiguration'; + +export class VerimatrixCoreDrmPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: VerimatrixCoreDrmConfiguration; + + constructor(configuration: VerimatrixCoreDrmConfiguration) { + this.contentProtectionConfiguration = configuration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'content-type': 'application/octet-stream', + Authorization: this.contentProtectionConfiguration.integrationParameters.drmToken ?? '' + }; + return request; + } +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..58177c53 --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,11 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import type { VerimatrixCoreDrmConfiguration } from './VerimatrixCoreDrmConfiguration'; +import { VerimatrixCoreDrmPlayReadyContentProtectionIntegration } from './VerimatrixCoreDrmPlayReadyContentProtectionIntegration'; + +export class VerimatrixCoreDrmPlayReadyContentProtectionIntegrationFactory + implements ContentProtectionIntegrationFactory +{ + build(configuration: VerimatrixCoreDrmConfiguration): ContentProtectionIntegration { + return new VerimatrixCoreDrmPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmWidevineContentProtectionIntegration.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..b4d54f3d --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,28 @@ +import type { CertificateRequest, ContentProtectionIntegration, LicenseRequest, MaybeAsync } from 'theoplayer'; +import type { VerimatrixCoreDrmConfiguration } from './VerimatrixCoreDrmConfiguration'; + +export class VerimatrixCoreDrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + private readonly contentProtectionConfiguration: VerimatrixCoreDrmConfiguration; + + constructor(configuration: VerimatrixCoreDrmConfiguration) { + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'content-type': 'application/octet-stream', + Authorization: this.contentProtectionConfiguration.integrationParameters.drmToken ?? '' + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.headers = { + ...request.headers, + 'content-type': 'application/octet-stream', + Authorization: this.contentProtectionConfiguration.integrationParameters.drmToken ?? '' + }; + return request; + } +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreDrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..3dcc3897 --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreDrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,11 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import type { VerimatrixCoreDrmConfiguration } from './VerimatrixCoreDrmConfiguration'; +import { VerimatrixCoreDrmWidevineContentProtectionIntegration } from './VerimatrixCoreDrmWidevineContentProtectionIntegration'; + +export class VerimatrixCoreDrmWidevineContentProtectionIntegrationFactory + implements ContentProtectionIntegrationFactory +{ + build(configuration: VerimatrixCoreDrmConfiguration): ContentProtectionIntegration { + return new VerimatrixCoreDrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/verimatrixcoredrm/VerimatrixCoreIntegrationParameters.ts b/drm/src/verimatrixcoredrm/VerimatrixCoreIntegrationParameters.ts new file mode 100644 index 00000000..20c4f16e --- /dev/null +++ b/drm/src/verimatrixcoredrm/VerimatrixCoreIntegrationParameters.ts @@ -0,0 +1,7 @@ +export interface VerimatrixCoreIntegrationParameters { + /** + * The drm token. + * + */ + drmToken?: string; +} diff --git a/drm/src/verimatrixcoredrm/barrel.ts b/drm/src/verimatrixcoredrm/barrel.ts new file mode 100644 index 00000000..0f1ca0d3 --- /dev/null +++ b/drm/src/verimatrixcoredrm/barrel.ts @@ -0,0 +1,9 @@ +export * from './VerimatrixCoreDrmConfiguration'; +export * from './VerimatrixCoreIntegrationParameters'; + +export * from './VerimatrixCoreDrmFairplayContentProtectionIntegration'; +export * from './VerimatrixCoreDrmFairplayContentProtectionIntegrationFactory'; +export * from './VerimatrixCoreDrmPlayReadyContentProtectionIntegration'; +export * from './VerimatrixCoreDrmPlayReadyContentProtectionIntegrationFactory'; +export * from './VerimatrixCoreDrmWidevineContentProtectionIntegration'; +export * from './VerimatrixCoreDrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/src/vudrm/VudrmDrmConfiguration.ts b/drm/src/vudrm/VudrmDrmConfiguration.ts new file mode 100644 index 00000000..54a8baaf --- /dev/null +++ b/drm/src/vudrm/VudrmDrmConfiguration.ts @@ -0,0 +1,50 @@ +import { DRMConfiguration } from 'theoplayer'; + +/** + * The identifier of the Vudrm integration. + */ +export type VudrmIntegrationID = 'vudrm'; + +/** + * Describes the configuration of the Vudrm DRM integration. + * + * ``` + * var drmConfiguration = { + * integration : 'vudrm', + * playready: { + * licenseAcquisitionURL: 'yourVudrmPlayReadyLicenseAcquisitionURL' + * }, + * widevine: { + * licenseAcquisitionURL: 'yourVudrmWidevineLicenseAcquisitionURL' + * }, + * integrationParameters: { + * token: 'PEtleU9T...blhNTD4=', + * keyId: ''; + * } + * } + * ``` + */ +export interface VudrmDrmConfiguration extends DRMConfiguration { + /** + * The identifier of the DRM integration. + */ + integration: VudrmIntegrationID; + + /** + * An object of key/value pairs which can be used to pass in specific parameters related to a source into a + * ContentProtectionIntegration + */ + integrationParameters: { + /** + * The authentication token. + */ + token: string; + + /** + * The key id. + * + *
- Only mandatory for Widevine. + */ + keyId?: string; + }; +} diff --git a/drm/src/vudrm/VudrmFairplayContentProtectionIntegration.ts b/drm/src/vudrm/VudrmFairplayContentProtectionIntegration.ts new file mode 100644 index 00000000..ea05dc84 --- /dev/null +++ b/drm/src/vudrm/VudrmFairplayContentProtectionIntegration.ts @@ -0,0 +1,54 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource, CertificateRequest } from 'theoplayer'; +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; +import { isVudrmDRMConfiguration } from './VudrmUtil'; +import { extractContentId } from '../utils/FairplayUtils'; +import { fromObjectToUint8Array, fromUint8ArrayToBase64String } from '../utils/TypeUtils'; + +export class VudrmFairplayContentProtectionIntegration implements ContentProtectionIntegration { + static readonly DEFAULT_CERTIFICATE_URL = 'https://fairplay-license.drm.technology/certificate'; + static readonly DEFAULT_LICENSE_URL = 'https://fairplay-license.drm.technology/license'; + + private readonly contentProtectionConfiguration: VudrmDrmConfiguration; + private contentId: string | undefined = undefined; + + constructor(configuration: VudrmDrmConfiguration) { + if (!isVudrmDRMConfiguration(configuration)) { + throw new Error('The FairPlay vuDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.url = + this.contentProtectionConfiguration.fairplay?.certificateURL ?? + VudrmFairplayContentProtectionIntegration.DEFAULT_CERTIFICATE_URL; + request.headers = { + ...request.headers, + 'Content-Type': 'application/json', + 'x-vudrm-token': this.contentProtectionConfiguration.integrationParameters.token + }; + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.url = + this.contentProtectionConfiguration.fairplay?.licenseAcquisitionURL ?? + VudrmFairplayContentProtectionIntegration.DEFAULT_LICENSE_URL; + const licenseParameters = { + token: this.contentProtectionConfiguration.integrationParameters.token, + contentId: this.contentId, + payload: fromUint8ArrayToBase64String(request.body!) + }; + request.headers = { + 'Content-Type': 'application/json', + 'x-vudrm-token': this.contentProtectionConfiguration.integrationParameters.token + }; + request.body = fromObjectToUint8Array(licenseParameters); + return request; + } + + extractFairplayContentId(skdUrl: string): string { + this.contentId = extractContentId(skdUrl); + return this.contentId; + } +} diff --git a/drm/src/vudrm/VudrmFairplayContentProtectionIntegrationFactory.ts b/drm/src/vudrm/VudrmFairplayContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..d975c2e0 --- /dev/null +++ b/drm/src/vudrm/VudrmFairplayContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; +import { VudrmFairplayContentProtectionIntegration } from './VudrmFairplayContentProtectionIntegration'; + +export class VudrmFairplayContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: VudrmDrmConfiguration): ContentProtectionIntegration { + return new VudrmFairplayContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/vudrm/VudrmPlayReadyContentProtectionIntegration.ts b/drm/src/vudrm/VudrmPlayReadyContentProtectionIntegration.ts new file mode 100644 index 00000000..83c48711 --- /dev/null +++ b/drm/src/vudrm/VudrmPlayReadyContentProtectionIntegration.ts @@ -0,0 +1,31 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource } from 'theoplayer'; +import { isVudrmDRMConfiguration } from './VudrmUtil'; +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; + +export class VudrmPlayReadyContentProtectionIntegration implements ContentProtectionIntegration { + static DEFAULT_LICENSE_URL = 'https://playready-license.drm.technology/rightsmanager.asmx'; + + private readonly contentProtectionConfiguration: VudrmDrmConfiguration; + + constructor(drmConfiguration: VudrmDrmConfiguration) { + if (!isVudrmDRMConfiguration(drmConfiguration)) { + throw new Error('The PlayReady vuDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = drmConfiguration; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + const url = new URL( + this.contentProtectionConfiguration.playready?.licenseAcquisitionURL ?? + VudrmPlayReadyContentProtectionIntegration.DEFAULT_LICENSE_URL + ); + if (this.contentProtectionConfiguration.playready?.queryParameters) { + for (const key of Object.keys(this.contentProtectionConfiguration.playready.queryParameters)) { + url.searchParams.set(key, this.contentProtectionConfiguration.playready.queryParameters[key]); + } + } + url.searchParams.set('token', this.contentProtectionConfiguration.integrationParameters.token); + request.url = url.toString(); + return request; + } +} diff --git a/drm/src/vudrm/VudrmPlayReadyContentProtectionIntegrationFactory.ts b/drm/src/vudrm/VudrmPlayReadyContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..e59efcc8 --- /dev/null +++ b/drm/src/vudrm/VudrmPlayReadyContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { VudrmPlayReadyContentProtectionIntegration } from './VudrmPlayReadyContentProtectionIntegration'; +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; + +export class VudrmPlayReadyContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: VudrmDrmConfiguration): ContentProtectionIntegration { + return new VudrmPlayReadyContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/vudrm/VudrmUtil.ts b/drm/src/vudrm/VudrmUtil.ts new file mode 100644 index 00000000..313df4e2 --- /dev/null +++ b/drm/src/vudrm/VudrmUtil.ts @@ -0,0 +1,5 @@ +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; + +export function isVudrmDRMConfiguration(configuration: VudrmDrmConfiguration): boolean { + return configuration.integrationParameters.token !== undefined; +} diff --git a/drm/src/vudrm/VudrmWidevineContentProtectionIntegration.ts b/drm/src/vudrm/VudrmWidevineContentProtectionIntegration.ts new file mode 100644 index 00000000..699d63ee --- /dev/null +++ b/drm/src/vudrm/VudrmWidevineContentProtectionIntegration.ts @@ -0,0 +1,40 @@ +import { ContentProtectionIntegration, LicenseRequest, MaybeAsync, BufferSource, CertificateRequest } from 'theoplayer'; +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; +import { isVudrmDRMConfiguration } from './VudrmUtil'; +import { fromObjectToUint8Array, fromUint8ArrayToNumberArray } from '../utils/TypeUtils'; + +export class VudrmWidevineContentProtectionIntegration implements ContentProtectionIntegration { + static readonly DEFAULT_LICENSE_URL = 'https://widevine-proxy.drm.technology/proxy'; + + private readonly contentProtectionConfiguration: VudrmDrmConfiguration; + + constructor(configuration: VudrmDrmConfiguration) { + if (!isVudrmDRMConfiguration(configuration)) { + throw new Error('The Widevine vuDRM token has not been correctly configured.'); + } + this.contentProtectionConfiguration = configuration; + } + + private wrapRequestBody(body: Uint8Array): Uint8Array { + const token = this.contentProtectionConfiguration.integrationParameters.token; + const drmInfo = fromUint8ArrayToNumberArray(body); + const kid = this.contentProtectionConfiguration.integrationParameters.keyId; + return fromObjectToUint8Array({ token, drm_info: drmInfo, kid }); + } + + onCertificateRequest(request: CertificateRequest): MaybeAsync | BufferSource> { + request.url = + this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL ?? + VudrmWidevineContentProtectionIntegration.DEFAULT_LICENSE_URL; + request.body = this.wrapRequestBody(request.body!); + return request; + } + + onLicenseRequest(request: LicenseRequest): MaybeAsync | BufferSource> { + request.url = + this.contentProtectionConfiguration.widevine?.licenseAcquisitionURL ?? + VudrmWidevineContentProtectionIntegration.DEFAULT_LICENSE_URL; + request.body = this.wrapRequestBody(request.body!); + return request; + } +} diff --git a/drm/src/vudrm/VudrmWidevineContentProtectionIntegrationFactory.ts b/drm/src/vudrm/VudrmWidevineContentProtectionIntegrationFactory.ts new file mode 100644 index 00000000..dfffdddb --- /dev/null +++ b/drm/src/vudrm/VudrmWidevineContentProtectionIntegrationFactory.ts @@ -0,0 +1,9 @@ +import type { ContentProtectionIntegration, ContentProtectionIntegrationFactory } from 'theoplayer'; +import { VudrmWidevineContentProtectionIntegration } from './VudrmWidevineContentProtectionIntegration'; +import { VudrmDrmConfiguration } from './VudrmDrmConfiguration'; + +export class VudrmWidevineContentProtectionIntegrationFactory implements ContentProtectionIntegrationFactory { + build(configuration: VudrmDrmConfiguration): ContentProtectionIntegration { + return new VudrmWidevineContentProtectionIntegration(configuration); + } +} diff --git a/drm/src/vudrm/barrel.ts b/drm/src/vudrm/barrel.ts new file mode 100644 index 00000000..b113c5c7 --- /dev/null +++ b/drm/src/vudrm/barrel.ts @@ -0,0 +1,9 @@ +export * from './VudrmDrmConfiguration'; +export * from './VudrmUtil'; + +export * from './VudrmFairplayContentProtectionIntegration'; +export * from './VudrmFairplayContentProtectionIntegrationFactory'; +export * from './VudrmPlayReadyContentProtectionIntegration'; +export * from './VudrmPlayReadyContentProtectionIntegrationFactory'; +export * from './VudrmWidevineContentProtectionIntegration'; +export * from './VudrmWidevineContentProtectionIntegrationFactory'; diff --git a/drm/test/axinomdrm/fairplay.html b/drm/test/axinomdrm/fairplay.html new file mode 100644 index 00000000..1a0d95d8 --- /dev/null +++ b/drm/test/axinomdrm/fairplay.html @@ -0,0 +1,49 @@ + + + + + Axinom Fairplay Test + + + + + +
+ + + diff --git a/drm/test/axinomdrm/playready.html b/drm/test/axinomdrm/playready.html new file mode 100644 index 00000000..d26f9b6e --- /dev/null +++ b/drm/test/axinomdrm/playready.html @@ -0,0 +1,48 @@ + + + + + Axinom PlayReady Test + + + + + +
+ + + diff --git a/drm/test/axinomdrm/widevine.html b/drm/test/axinomdrm/widevine.html new file mode 100644 index 00000000..51094f38 --- /dev/null +++ b/drm/test/axinomdrm/widevine.html @@ -0,0 +1,48 @@ + + + + + Axinom Widevine Test + + + + + +
+ + + diff --git a/drm/test/azuredrm/fairplay.html b/drm/test/azuredrm/fairplay.html new file mode 100644 index 00000000..014b7b1b --- /dev/null +++ b/drm/test/azuredrm/fairplay.html @@ -0,0 +1,48 @@ + + + + + Azure drm FairPlay Test + + + + + +
+ + + diff --git a/drm/test/azuredrm/playready.html b/drm/test/azuredrm/playready.html new file mode 100644 index 00000000..3e98faef --- /dev/null +++ b/drm/test/azuredrm/playready.html @@ -0,0 +1,47 @@ + + + + + Azure drm PlayReady Test + + + + + +
+ + + diff --git a/drm/test/azuredrm/widevine.html b/drm/test/azuredrm/widevine.html new file mode 100644 index 00000000..d5032686 --- /dev/null +++ b/drm/test/azuredrm/widevine.html @@ -0,0 +1,47 @@ + + + + + Azure drm Widevine Test + + + + + +
+ + + diff --git a/drm/test/castlabs/fairplay.html b/drm/test/castlabs/fairplay.html new file mode 100644 index 00000000..7ab7fdce --- /dev/null +++ b/drm/test/castlabs/fairplay.html @@ -0,0 +1,58 @@ + + + + + CastLabs FairPlay Test + + + + + +
+ + + diff --git a/drm/test/castlabs/playready.html b/drm/test/castlabs/playready.html new file mode 100644 index 00000000..90b50f26 --- /dev/null +++ b/drm/test/castlabs/playready.html @@ -0,0 +1,56 @@ + + + + + CastLabs PlayReady Test + + + + + +

+ This example uses the integration documented at + https://docs.theoplayer.com/how-to-guides/04-drm/02-castlabs-drmtoday/00-introduction.md. +

+
+ + + diff --git a/drm/test/castlabs/widevine.html b/drm/test/castlabs/widevine.html new file mode 100644 index 00000000..6b4b72da --- /dev/null +++ b/drm/test/castlabs/widevine.html @@ -0,0 +1,56 @@ + + + + + CastLabs Widevine Test + + + + + +

+ This example uses the integration documented at + https://docs.theoplayer.com/how-to-guides/04-drm/02-castlabs-drmtoday/00-introduction.md. +

+
+ + + diff --git a/drm/test/comcastdrm/fairplay.html b/drm/test/comcastdrm/fairplay.html new file mode 100644 index 00000000..2c11d5ab --- /dev/null +++ b/drm/test/comcastdrm/fairplay.html @@ -0,0 +1,102 @@ + + + + + Comcast DRM FairPlay Test + + + + + +
+
+

Comcast DRM FairPlay

+

+ Enter valid data below, and hit play. Only use this test player on FairPlay-enabled platforms, such + as Safari. +

+ + + + + + + + + + + + + +
+
+
+
+
+ + + diff --git a/drm/test/comcastdrm/playready.html b/drm/test/comcastdrm/playready.html new file mode 100644 index 00000000..cad04330 --- /dev/null +++ b/drm/test/comcastdrm/playready.html @@ -0,0 +1,97 @@ + + + + + Comcast DRM PlayReady Test + + + + + + +
+
+

Comcast DRM PlayReady

+

+ Enter valid data below, and hit play. Only use this test player on PlayReady-enabled platforms, such + as Edge. +

+ + + + + + + + + + + + + +
+
+
+
+
+ + + diff --git a/drm/test/comcastdrm/style.css b/drm/test/comcastdrm/style.css new file mode 100644 index 00000000..0ad72499 --- /dev/null +++ b/drm/test/comcastdrm/style.css @@ -0,0 +1,17 @@ +.row { + display: flex; +} + +.column { + flex: 50%; + padding: 5px; +} + +input[type='text'] { + width: 100%; +} + +#update { + margin-top: 5px; + display: block; +} diff --git a/drm/test/comcastdrm/widevine.html b/drm/test/comcastdrm/widevine.html new file mode 100644 index 00000000..3970efed --- /dev/null +++ b/drm/test/comcastdrm/widevine.html @@ -0,0 +1,147 @@ + + + + + Comcast DRM Widevine Test + + + + + + +
+
+

Comcast DRM Widevine

+

+ Enter valid data below, and hit play. Only use this test player on Widevine-enabled platforms, such + as Chrome. +

+ + + + + + + + + + + + + +
+
+
+
+
+ + + diff --git a/drm/test/ezdrm/fairplay.html b/drm/test/ezdrm/fairplay.html new file mode 100644 index 00000000..ffcd3b8f --- /dev/null +++ b/drm/test/ezdrm/fairplay.html @@ -0,0 +1,45 @@ + + + + + Ezdrm Fairplay Test + + + + + +
+ + + diff --git a/drm/test/irdetocontrol/fairplay.html b/drm/test/irdetocontrol/fairplay.html new file mode 100644 index 00000000..17eb5896 --- /dev/null +++ b/drm/test/irdetocontrol/fairplay.html @@ -0,0 +1,59 @@ + + + + + Irdeto Control FairPlay Test + + + + + +
+ + + diff --git a/drm/test/irdetocontrol/playready.html b/drm/test/irdetocontrol/playready.html new file mode 100644 index 00000000..9769e35f --- /dev/null +++ b/drm/test/irdetocontrol/playready.html @@ -0,0 +1,42 @@ + + + + + Irdeto Control PlayReady Test + + + + + +
+ + + diff --git a/drm/test/irdetocontrol/widevine.html b/drm/test/irdetocontrol/widevine.html new file mode 100644 index 00000000..db9c07ca --- /dev/null +++ b/drm/test/irdetocontrol/widevine.html @@ -0,0 +1,42 @@ + + + + + Irdeto Control Widevine Test + + + + + +
+ + + diff --git a/drm/test/keyos/fairplay.html b/drm/test/keyos/fairplay.html new file mode 100644 index 00000000..385284ed --- /dev/null +++ b/drm/test/keyos/fairplay.html @@ -0,0 +1,51 @@ + + + + + KeyOS drm FairPlay Test + + + + + +
+ + + diff --git a/drm/test/keyos/playready.html b/drm/test/keyos/playready.html new file mode 100644 index 00000000..65bc361a --- /dev/null +++ b/drm/test/keyos/playready.html @@ -0,0 +1,49 @@ + + + + + KeyOS drm PlayReady Test + + + + + +
+ + + diff --git a/drm/test/keyos/widevine.html b/drm/test/keyos/widevine.html new file mode 100644 index 00000000..dcac0fd5 --- /dev/null +++ b/drm/test/keyos/widevine.html @@ -0,0 +1,49 @@ + + + + + KeyOS drm Widevine Test + + + + + +
+ + + diff --git a/drm/test/nagradrm/fairplay.html b/drm/test/nagradrm/fairplay.html new file mode 100644 index 00000000..e35d912a --- /dev/null +++ b/drm/test/nagradrm/fairplay.html @@ -0,0 +1,94 @@ + + + THEOplayer Web SDK Nagra FairPlay + + + < + + + + + +
+
+
+
+
+

Enter valid data below, and hit play.

+ + + + + + + + + + + +
+
+ + + + diff --git a/drm/test/nagradrm/playready.html b/drm/test/nagradrm/playready.html new file mode 100644 index 00000000..d751e00f --- /dev/null +++ b/drm/test/nagradrm/playready.html @@ -0,0 +1,109 @@ + + + THEOplayer Web SDK Nagra PlayReady + + + + + + + + +
+
+
+
+
+

Enter valid data below, and hit play.

+ + + + + + + +
+
+ + + + diff --git a/drm/test/nagradrm/style.css b/drm/test/nagradrm/style.css new file mode 100644 index 00000000..7a566ba0 --- /dev/null +++ b/drm/test/nagradrm/style.css @@ -0,0 +1,16 @@ +.row { + display: flex; +} + +.column { + flex: 50%; + padding: 5px; +} + +input[type='text'] { + width: 100%; +} + +#update { + margin-top: 5px; +} diff --git a/drm/test/nagradrm/widevine.html b/drm/test/nagradrm/widevine.html new file mode 100644 index 00000000..90a80a01 --- /dev/null +++ b/drm/test/nagradrm/widevine.html @@ -0,0 +1,109 @@ + + + THEOplayer Web SDK Nagra Widevine + + + + + + + + +
+
+
+
+
+

Enter valid data below, and hit play.

+ + + + + + + +
+
+ + + + diff --git a/drm/test/titaniumdrm/fairplay.html b/drm/test/titaniumdrm/fairplay.html new file mode 100644 index 00000000..3e520cb7 --- /dev/null +++ b/drm/test/titaniumdrm/fairplay.html @@ -0,0 +1,69 @@ + + + + + Titanium DRM Fairplay Test Using AuthToken or device parameters + + + + + +
+ + + diff --git a/drm/test/titaniumdrm/playready.html b/drm/test/titaniumdrm/playready.html new file mode 100644 index 00000000..814e0ae2 --- /dev/null +++ b/drm/test/titaniumdrm/playready.html @@ -0,0 +1,69 @@ + + + + + Titanium DRM PlayReady Test using AuthToken or device parameters + + + + + +
+ + + diff --git a/drm/test/titaniumdrm/widevine.html b/drm/test/titaniumdrm/widevine.html new file mode 100644 index 00000000..be7f7564 --- /dev/null +++ b/drm/test/titaniumdrm/widevine.html @@ -0,0 +1,71 @@ + + + + + Titanium DRM Widevine Test Using AuthToken or device parameters + + + + + +
+ + + diff --git a/drm/test/verimatrixcoredrm/fairplay.html b/drm/test/verimatrixcoredrm/fairplay.html new file mode 100644 index 00000000..756697f8 --- /dev/null +++ b/drm/test/verimatrixcoredrm/fairplay.html @@ -0,0 +1,54 @@ + + + + + Verimatrix Core DRM FairPlay Test + + + + + +
+ + + diff --git a/drm/test/verimatrixcoredrm/playready.html b/drm/test/verimatrixcoredrm/playready.html new file mode 100644 index 00000000..cdd3e253 --- /dev/null +++ b/drm/test/verimatrixcoredrm/playready.html @@ -0,0 +1,52 @@ + + + + + Verimatrix Core DRM PlayReady Test + + + + + +
+ + + diff --git a/drm/test/verimatrixcoredrm/widevine.html b/drm/test/verimatrixcoredrm/widevine.html new file mode 100644 index 00000000..39ee769d --- /dev/null +++ b/drm/test/verimatrixcoredrm/widevine.html @@ -0,0 +1,52 @@ + + + + + Verimatrix Core DRM Widevine Test + + + + + +
+ + + diff --git a/drm/test/vudrm/fairplay.html b/drm/test/vudrm/fairplay.html new file mode 100644 index 00000000..966d82a1 --- /dev/null +++ b/drm/test/vudrm/fairplay.html @@ -0,0 +1,48 @@ + + + + + Vudrm Fairplay Test + + + + + +
+ + + diff --git a/drm/test/vudrm/playready.html b/drm/test/vudrm/playready.html new file mode 100644 index 00000000..1be0a0bd --- /dev/null +++ b/drm/test/vudrm/playready.html @@ -0,0 +1,47 @@ + + + + + Vudrm PlayReady Test + + + + + +
+ + + diff --git a/drm/test/vudrm/widevine.html b/drm/test/vudrm/widevine.html new file mode 100644 index 00000000..6499db3c --- /dev/null +++ b/drm/test/vudrm/widevine.html @@ -0,0 +1,48 @@ + + + + + Vudrm Widevine Test + + + + + +
+ + + diff --git a/drm/tsconfig.json b/drm/tsconfig.json new file mode 100644 index 00000000..d416c1ae --- /dev/null +++ b/drm/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist/types" + }, + "include": [ + "src/**/*" + ] +} diff --git a/drm/typedoc.json b/drm/typedoc.json new file mode 100644 index 00000000..14821d3f --- /dev/null +++ b/drm/typedoc.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "extends": [ + "../typedoc.base.json" + ], + "entryPoints": [ + "src/index.ts" + ], + "tsconfig": "tsconfig.json", + "readme": "README.md", + "name": "DRM Connectors" +} diff --git a/package-lock.json b/package-lock.json index 4df815c9..b2ad9fb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "cmcd", "comscore", "adscript", - "gemius" + "gemius", + "drm" ], "devDependencies": { "@changesets/cli": "^2.27.1", @@ -86,9 +87,17 @@ } } }, + "drm": { + "name": "@theoplayer/drm-connector-web", + "version": "0.0.0", + "license": "MIT", + "peerDependencies": { + "theoplayer": "^7.0.0" + } + }, "gemius": { "name": "@theoplayer/gemius-connector-web", - "version": "0.0.1", + "version": "0.1.0", "license": "MIT", "peerDependencies": { "theoplayer": "^7.0.0" @@ -2430,6 +2439,10 @@ "resolved": "conviva", "link": true }, + "node_modules/@theoplayer/drm-connector-web": { + "resolved": "drm", + "link": true + }, "node_modules/@theoplayer/gemius-connector-web": { "resolved": "gemius", "link": true diff --git a/package.json b/package.json index 2676258a..0ffb733b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "cmcd", "comscore", "adscript", - "gemius" + "gemius", + "drm" ], "scripts": { "changeset:version": "changeset version && node .changeset/post-process.js",