- Overview
- Creating a new integration
- Request and Response types
- Available examples
- Testing an integration
- Conclusion
This document provides a step-by-step approach on how to create a custom DRM integration with the THEOplayer iOS SDK. It gives an overview of which classes are involved and how they participate in the DRM flow.
The project's top-level README elaborates on what it means to create a DRM integration, gives a brief platform-independent overview of the flow, and clarifies the terminology being used throughout the document. Make sure to familiarise yourself with it before starting your own implementation.
The static class diagram below depicts classes that are part of the SDK in white, while coloured classes need to be
implemented when creating a custom integration. The ContentProtectionIntegrationFactory
subclass is only used once
by THEOplayer to create an instance of a ContentProtectedIntegration
subclass. The latter contains the actual hook methods
being referred to in the DRM integration sequence diagram.
In the next section a custom integration is built by implementing both subclasses.
First create a custom implementation of ContentProtectionIntegration
under ios/ContentProtectionIntegration/integration
.
This object defines handler methods that allow altering license and certificate requests and responses as part of the
DRM flow.
All methods are optional. They can be omitted if the integration does not require additional action, in which case the
default implementation will be used.
import Foundation
import THEOplayerSDK
class MyCustomIntegration: ContentProtectionIntegration {
static let integrationID = "myCustomDRM"
let configuration: DRMConfiguration
init(configuration: DRMConfiguration) {
self.configuration = configuration
}
func onCertificateRequest(request: CertificateRequest, callback: CertificateRequestCallback) {
callback.request(request: request)
}
func onCertificateResponse(response: CertificateResponse, callback: CertificateResponseCallback) {
callback.respond(certificate: response.body)
}
func onLicenseRequest(request: LicenseRequest, callback: LicenseRequestCallback) {
// request.headers = [
// "x-token": self.getTokenFromDrmConfiguration(),
// ]
callback.request(request: request)
}
func onLicenseResponse(response: LicenseResponse, callback: LicenseResponseCallback) {
callback.respond(license: response.body)
}
func extractFairplayContentId(skdUrl: String) -> String {
return "<contentId>"
}
private func getTokenFromDrmConfiguration() -> String {
guard let token = self.configuration.integrationParameters?["token"] as? String else {
fatalError("Could not find the token in the integrationParameters.")
}
return token
}
}
Optional parameters needed for certificate or license requests, such as tokens, can be added to a
DRMConfiguration
object that is passed when creating instances of the MyCustomIntegration
class.
In the example, MyCustomIntegration
adds a token from the configuration object as part of the headers
during a license request.
Next, create a ContentProtectionIntegrationFactory for building MyCustomIntegration instances.
THEOplayer will use this factory in its DRM flow whenever it needs a ContentProtectionIntegration instance that
matches with the content protected source. How THEOplayer knows which factory to take will be determined in the
registerContentProtectionIntegration
step next.
class MyCustomIntegrationFactory: ContentProtectionIntegrationFactory {
func build(configuration: DRMConfiguration) -> ContentProtectionIntegration {
return MyCustomIntegration(configuration: configuration)
}
}
An instance of MyCustomIntegrationFactory
needs to be registered with THEOplayer's global instance in
AppDelegate
by specifying a unique integrationId
, such as "myCustomDRM"
in this example.
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
THEOplayer.registerContentProtectionIntegration(integrationId: MyCustomIntegration.integrationID, keySystem: .FAIRPLAY, integrationFactory: MyCustomIntegrationFactory())
return true
}
}
When the player now loads a source with a customIntegrationId
that matches the integrationId
passed during registration, an instance of MyCustomIntegration
will be created and used in the DRM flow.
Also, fill the source description in ViewController, which provides the manifest, certificate and license URLs along with any integration parameters.
Note: The Dictionary integrationParameters
only supports Primitive-types and Codable values. all other types will ignored
var sampleSource: SourceDescription {
return SourceDescription(
source: TypedSource(
src: "<insert_manifest_here>",
type: "application/x-mpegurl",
drm: FairPlayDRMConfiguration(
customIntegrationId: MyCustomIntegration.integrationID,
licenseAcquisitionURL: "<insert_license_url>",
certificateURL: "<insert_certificate_url>",
integrationParameters: [
// optional integration parameters
// "token": "<insert_token>"
]
)
)
)
}
Finally, build and run the app on an iOS device.
Manipulating certificate and license requests and responses requires special care. Next to adding header
fields or changing the target url, the bodies of CertificateRequest
andLicenseRequest
most often
need to be transformed or wrapped before passing it along.
@objc public class CertificateRequest : THEOplayerSDK.Request {
@objc override public init(
url: String,
method: String,
headers: [String : String],
body: Data?
)
required public init(from decoder: Decoder) throws
}
@objc public class LicenseRequest : THEOplayerSDK.Request {
@objc public var fairplaySkdUrl: String?
@objc override public init(
url: String,
method: String,
headers: [String : String],
body: Data?
)
@objc public init(
url: String,
method: String,
headers: [String : String],
body: Data?,
fairplaySkdUrl: String?,
useCredentials: Bool
)
}
Both CertificateRequest
and LicenseRequest
objects expect the body to be of byte buffer type
Data.
In case of a license request it originally contains the challenge generated by the CDM.
A common way of passing extra data to the server is by wrapping the raw request body
in a JSON object with some additional properties, which is then transformed back into the required type Data
. The following
example in Swift is taken from the VuDRM integration sample:
var dict = [String: String]()
dict.updateValue(self.getTokenFromDrmConfiguration(), forKey: "token")
dict.updateValue(contentID, forKey: "contentId")
if let payload = fromDataToBase64String(data: request.body) {
dict.updateValue(payload, forKey: "payload")
}
do {
request.body = try JSONEncoder().encode(dict)
callback.request(request: request)
} catch let error {
fatalError(error.localizedDescription)
}
where the fromDataToBase64String
helper method creates a base64-encoded string from the request body.
Similarly, CertificateResponse
and LicenseResponse
objects returned from the server contain among others the
response headers and the response body.
The latter is again of type Data,
containing the certificate or license that will be passed to the CDM.
@objc public class CertificateResponse : THEOplayerSDK.Response {
@objc public init(
certificateRequest: THEOplayerSDK.CertificateRequest,
url: String,
status: Int,
statusText: String,
headers: [String : String],
body: Data
)
}
@objc public class LicenseResponse : THEOplayerSDK.Response {
@objc public init(
licenseRequest: THEOplayerSDK.LicenseRequest,
url: String,
status: Int,
statusText: String,
headers: [String : String],
body: Data
)
}
Depending on the DRM integration, the response body either already is a raw certificate or license that can be passed along as-is, or needs to be transformed or unwrapped first in a way similar to the request body.
- Vualto VuDRM
- Microsoft Azure DRM
- Verizon Uplynk DRM
- EZ DRM
- Verimatrix Core DRM
- Arris Titanium DRM (supported from THEOplayer 4.4.0)
- Ensure that your Podfile is point to an appropriate THEOplayer iOS SDK.
- Run
pod install --repo-update
in theios/
folder to install your Podfile. - Open the
.xcworkspace
file instead of the.xcodeproj
file. - Enter a valid license key in
ViewController.swift
instead of"YOUR_LICENSE_HERE"
. - Make sure to fill in the necessary fields
ViewController
for the content integration that will be tested, such as the manifest url and any integration parameters. - Attach an iOS device, and finally build and run the project.
This document showed how to create a custom DRM integration for iOS using THEOplayer's Content Integration API, and register it with THEOplayer. The iOS integration API can slightly differ on other platforms, so it is best to check the platform's specific document.