Digital Rights Management is a technology used to protect and manage the distribution of digital content, including streaming media. DRM systems aim to prevent unauthorized access, copying, redistribution, and piracy of digital content by implementing various security measures.
Configuration
All DRM-related settings can be found in DrmConfiguration, which is an optional component of PlayerConfiguration.
These settings can be specified before creating a player like so:
let playerConfig = PlayerConfiguration(with: streamUrl, contentType: .hls)
playerConfig.drmConfiguration = DrmConfiguration(with: .drmToday)
playerConfig.drmConfiguration?.environment = .staging
playerConfig.drmConfiguration?.userId = "..."
playerConfig.drmConfiguration?.sessionId = "..."
playerConfig.drmConfiguration?.merchant = "..."
playerConfig.drmConfiguration?.assetId = "..."
playerConfig.drmSystem = .fairplay
let player = PRESTOplaySDK.shared.player(for: playerConfig)
If the drmConfiguration is left empty (nil), then it is assumed that there is no DRM system used and the stream is clear (unencrypted).
PlayerConfiguration has the property drmSystem which by default will set to .auto to attempt to automatically choose the DRM system used. However this can be explicitly set for example to .widevine.
Summary of DrmConfiguration properties
| Property | Description |
|---|---|
environment |
DRM backend environment selector. |
base |
Optional backend base URL hint. |
preventSecondScreenPlayback |
Enables secondary-screen protection checks where supported. |
userId |
Provider-specific user identifier. |
sessionId |
Provider-specific session identifier. |
merchant |
Merchant/account identifier used by the DRM backend. |
assetId |
Asset identifier used by the DRM backend. |
variantId |
Variant identifier used by the DRM backend. |
authToken |
Authorization token used in DRM requests. |
trackingToken |
Tracking token used by backend integrations. |
keyUri |
Content key URI used by key retrieval flows. |
license |
Inline license payload (Data). |
licensingUrl |
License server URL. |
licensingParameters |
Additional license-request parameters. |
certificate |
Inline certificate payload (Data). |
certificateUrl |
Certificate server URL. |
certificateParameters |
Additional certificate-request parameters. |
persistLicense |
Enables persistent-license behavior for offline-capable flows. |
FairPlay Streaming (FPS)
FairPlay is a digital rights management (DRM) system developed by Apple Inc. It is specifically designed to protect and control the distribution of digital content, such as video and audio, in Apple’s ecosystem, including iTunes, Apple TV, and other Apple devices.
Set up FairPlay Streaming with the following steps:
- Prepare your HLS playlist by including the following
#EXT-X-KEYentry
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://"
- Set the
DrmConfigurationobject parameters
let playerConfig = PlayerConfiguration(with: streamUrl)
playerConfig.drmConfiguration = DrmConfiguration(with: .drmToday)
playerConfig.drmConfiguration?.environment = .staging
playerConfig.drmConfiguration?.userId = "..."
playerConfig.drmConfiguration?.sessionId = "..."
playerConfig.drmConfiguration?.merchant = "..."
playerConfig.drmConfiguration?.assetId = "..."
playerConfig.drmSystem = .fairplay
- (Optional) If you use a backend different than DRMToday, you need to set additional values for license and provisioning certificate requests
playerConfig.drmConfiguration?.licensingUrl = "..."
playerConfig.drmConfiguration?.certificateUrl = "..."
- (Optional) You can also set the assetId and variantId in the manifest. Note: if these values are set in the DrmConfiguration, then the values parsed from the manifest will be overwritten.
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://drmtoday?assetId=yourAssetId&variantId=yourVariantId"
Widevine (WV)
Google Widevine is a digital rights management (DRM) solution developed by Google to protect and secure digital content distribution, particularly for video streaming and media playback. It is designed to prevent unauthorized copying and distribution of copyrighted content, ensuring that content providers can control how their content is consumed and accessed by users.
Security Levels: Widevine offers different security levels (L1, L2, L3) that correspond to different hardware and software capabilities on user devices. L1, the highest level, typically involves hardware-based DRM protection, making it more secure than L2 and L3.
CastlabsVTWithWidevine is an L3 implementation.
Set up Widevine with the following steps:
- Prepare your DASH manifest by including the required encryption configuration. Example can be found here:
https://demo.cf.castlabs.com/media/bbb_abr/Manifest.mpd
- Set the
DrmConfigurationobject parameters
let playerConfig = PlayerConfiguration(with: streamUrl)
playerConfig.drmConfiguration = DrmConfiguration(with: .drmToday)
playerConfig.drmConfiguration?.environment = .production
playerConfig.drmConfiguration?.userId = "..."
playerConfig.drmConfiguration?.sessionId = "..."
playerConfig.drmConfiguration?.merchant = "..."
playerConfig.drmConfiguration?.assetId = "..."
playerConfig.drmSystem = .widevine
- (Optional) If you use a backend different than DRMToday, you need to set additional values for license and provisioning certificate requests
playerConfig.drmConfiguration?.licensingUrl = "..."
The CastlabsVTWithWidevine plugin tightly cooperates with CastlabsVT (the .castlabs playback engine). Enable and register both plugins in the SDK to play protected content.
Clear Key
Clear Key DRM is a simple encryption method for content protection. To play Clear Key protected content, set the playerConfig.drmSystem to .clear_key.
```swift
playerConfig.contentUrl = URL(string: "https://demo.cf.castlabs.com/media/QA/oceans_aes/oceans_aes_drmtodayS_only_kid.m3u8")!
playerConfig.drmConfiguration?.userId = "..."
playerConfig.drmConfiguration?.sessionId = "..."
playerConfig.drmConfiguration?.merchant = "..."
playerConfig.contentType = .hls
playerConfig.drmSystem = .clear_key
```
When PRESTOplaySDK.shared.enableClearKeyCache is set to true, Clear Key licenses are stored locally in memory. These caches are automatically destroyed when the application is closed.
```swift
PRESTOplaySDK.shared.enableClearKeyCache = true
```
Request interceptors
You can intercept HTTP/S requests and responses issued for DRM-related steps through RequestModifier and ResponseModifier classes.
Example request and response modifier:
struct RequestModifier: RequestModifierProtocol {
func modify(_ request: Request,
completionHandler: @escaping (Request?) -> Void) {
completionHandler(request)
}
}
struct ResponseModifier: ResponseModifierProtocol {
func modify(_ response: Response,
completionHandler: @escaping (Response?) -> Void) {
completionHandler(response)
}
}
To install the interception at the SDK level:
PRESTOplaySDK.shared.requestModifiers.append(requestModifier)
PRESTOplaySDK.shared.responseModifiers.append(responseModifier)
To install the interception at player level:
let player = PRESTOplaySDK.shared.player()
// ...
player.requestModifiers = [requestModifier]
player.responseModifiers = [responseModifier]
A sample demo DRMTodayDemo, is provided to demonstrate integration with DRMToday. This approach can also be adapted for other DRM backends.
To intentionally fail a given Response, set its data field to nil.
Accessing DRMToday error codes
There are two integration paths:
- Use request/response modifiers to check directly the response from DRMtoday.
- Inspect
PRESTOerrorin your playback callbacks.
1. Request/response modifiers
Use this when you want explicit DRMtoday error from license/provisioning responses.
struct ResponseModifier: ResponseModifierProtocol {
func modify(
_ response: Response,
completionHandler: @escaping (Response?, PRESTOerror?) -> Void
) {
if response.type == .license,
let error = Utils.mapToDRMTodayErrors(response) {
completionHandler(response, error)
return
}
// Optional: manually parse DRMtoday headers/body here and map to your own app error model.
completionHandler(response, nil)
}
func modify(_ response: Response, completionHandler: @escaping (Response?) -> Void) {
completionHandler(response)
}
}
Attach this modifier at SDK level (PRESTOplaySDK.shared.responseModifiers) or player level (player.responseModifiers).
2. Check PRESTOerror properties
Use this in playback callbacks (player.error, onState, onError) to read the propagated details:
if let error = player.error, error.type == .drm_today {
let responseCode = error.data["DRMTodayResponseCode"] // DRMtoday x-dt-resp-code, if present
let parent = error.parentError as NSError?
let mappedCode = parent?.code // mapped DRMtoday exception id
let mappedMessage = parent?.localizedDescription // mapped DRMtoday message
}
Offline licensing and prefetching
Use PRESTOplaySDK.shared.prefetcher(for:) to retrieve DRM keys before playback/download.
This is the public API for offline-license prefetching.
var config = PlayerConfiguration(with: streamUrl)
config.drmConfiguration = DrmConfiguration(with: .drmToday)
config.drmConfiguration?.persistLicense = true
guard let prefetcher = PRESTOplaySDK.shared.prefetcher(for: config) else {
return
}
guard prefetcher.canPrefetch(config) else {
return
}
prefetcher.prefetchKeys { error in
if let error {
// Handle prefetch failure
return
}
// Prefetch completed
}
Prefetcher API surface
canPrefetch(_:): check if the current configuration is supported by the active DRM implementation.prefetchKeys(_:): trigger key acquisition.deleteKeys(): remove keys for the current content scope (engine-specific behavior).deleteAllPersistentData(): remove all stored DRM key material/metadata.playDurationLeft(...): remaining play window, when available.storageDurationLeft(...): remaining storage window, when available.
FairPlay-specific notes
- Offline FairPlay HLS should expose keys using
EXT-X-SESSION-KEY(instead ofEXT-X-KEY) so eligible keys can be prefetched (source):
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI="skd://"
- For offline playback from local files, use the local URL as
contentUrl. PlayerConfiguration(with: localUrl)restores DRM-related fields from downloader metadata when available (includingoriginUrlfor protected offline playback).
Widevine-specific notes
- Widevine prefetching is available through
CastlabsVTWithWidevinewith the.castlabsplayback engine. - Use the same prefetcher flow (
canPrefetch->prefetchKeys) and handle completion errors from the callback.
Expiry inspection
After successful prefetch, use duration APIs to inspect license windows:
let playLeft = prefetcher.playDurationLeft()
let storageLeft = prefetcher.storageDurationLeft()
Meaning of each value:
playDurationLeft: remaining time the user can continue playback once the play window is active.storageDurationLeft: remaining time the stored/offline license remains valid on device.
Availability of both values depends on DRM backend and license policy.
Dual license expiry for offline rentals
To limit the license usage time. Dual expiry window is used:
playDurationcan be defined to limit total play duration of a streamexpirationDatecan be defined to set the absolute date when stream will no longer be possible to play within the license.
Upfront authentication token
A token used for upfront authentication is considered invalid after 10 minutes of its creation or because it was already used by another device. If the client attempts to get a license with an invalid token DRMToday will reply with a 403.
To renew an expired authentication token, implement the PRESTOplaySDK.shared.onDeviceAuthTokenRequired() method to provide a new authentication key. For example,
PRESTOplaySDK.shared.onDeviceAuthTokenRequired = { [weak self] playerConfiguration in
guard let self,
let configurationUrl = playerConfiguration.configurationUrl else {
return nil
}
var authToken: String?
let group = DispatchGroup()
group.enter()
Utils.loadAuthToken(from: configurationUrl, userToken: self.userToken) { token in
authToken = token
group.leave()
}
let _ = group.wait(timeout: .now() + 10)
return authToken
}
In the example code, the userToken is a key obtained from your DRM provider.
HDCP protection level support
PRESTOplay does not expose a public API to read or force a specific HDCP level (for example HDCP 1.x vs 2.x). The effective HDCP requirement is resolved by platform DRM and license policy at runtime.
For PlayerEngine.apple:
- HDCP/output protection requirements come from FairPlay content policy (license server + stream signaling).
- In HLS/FairPlay workflows, HDCP policy is typically signaled and enforced by the FairPlay pipeline (for example via playlist and license policy).
- The final behavior depends on sink and route capabilities (for example HDMI path, adapter, display, mirroring route).
- When output protection is not sufficient, use SDK integration signals:
throwFatalErrorOnContentObscuredcallback- DRM/system playback errors (for example second-screen protection errors when enabled)
For castLabs playback (PlayerEngine.castlabs, including GStreamer-based pipelines configured through player setup):
- There is no public API to select/query HDCP level directly.
- Use SDK protection controls to enforce output/capture policy in the app:
DrmConfiguration.preventSecondScreenPlaybackPlayerAPI.protectedWithoutDRMfor protected content flows without a DRM license session.
In short: HDCP level selection is platform/license driven.
Second-screen and screen-capture protection
Second-screen output (for example AirPlay, mirroring, and external displays) and on-device screen recording are handled through SDK protection controls and runtime errors.
Public controls
DrmConfiguration.preventSecondScreenPlayback(defaulttrue)PlayerAPI.protectedWithoutDRMPlayerAPI.throwFatalErrorOnContentObscured(FairPlay HLS onPlayerEngine.apple)
protectedWithoutDRM is intended for streams protected by transport/security policy outside SDK DRM workflows.
When enabled, the player applies capture-protection behavior even if no DRM configuration is attached.
Runtime behavior
If drmConfiguration.preventSecondScreenPlayback == true:
- Second-screen/external-display output triggers
.drm_second_screen_detected. - Screen capture/recording triggers
.drm_screen_recording_detected. - Playback is stopped by the SDK.
If drmConfiguration.preventSecondScreenPlayback == false:
- SDK second-screen/capture checks are not enforced.
- DRM/provider/platform policy can still restrict playback.
If player.protectedWithoutDRM == true:
- Capture protection is enforced even without a DRM session.
- This is useful for protected non-DRM playback pipelines (for example externally managed key delivery).
Recommended integration
- Keep
drmConfiguration.preventSecondScreenPlayback = truefor protected content. - Set
player.protectedWithoutDRM = truewhen content requires capture safeguards without a DRM session. - Handle
.drm_second_screen_detectedand.drm_screen_recording_detectedin SDK error callbacks. - For
PlayerEngine.apple, optionally usethrowFatalErrorOnContentObscuredto control app behavior when output becomes obscured.
throwFatalErrorOnContentObscured behavior (PlayerEngine.apple)
Use this callback when you want explicit control over app behavior when player reports obscured output caused by insufficient external protection.
- Called with
obscured == truewhen output becomes obscured. - Called with
obscured == falsewhen output is no longer obscured. - Return
trueto let the SDK raise a playback error for the obscured state. - Return
falseto avoid raising that error and wait for output conditions to improve. - If callback is not set, SDK uses default behavior and raises the playback error on obscured output.
Example:
player.throwFatalErrorOnContentObscured = { obscured in
if obscured {
// Update UI and pause user interaction if needed.
// Return true to fail playback, false to wait.
return true
}
// Output protection is restored.
return false
}
Protect content without DRM
You can enable content protection without using DRM by manually setting the protectedWithoutDRM property of the player. When enabled, the player will enforce screen capture protection.
This is useful when the decryption key is obtained by other means, such as when HLS content is protected using standard transport method.
This property does not create a DRM license session. It enables player-side protection checks/capture safeguards for non-DRM playback pipelines.
player.protectedWithoutDRM = true