Skip to content

Commit d750b72

Browse files
committed
Added DRM for FastPix iOS Player
1 parent 66ddc52 commit d750b72

File tree

7 files changed

+237
-48
lines changed

7 files changed

+237
-48
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.2.0]
6+
7+
- FastPix iOS Player now supports DRM via Apple FairPlay for content protection.
8+
59
## [0.1.0]
610

711
### Added

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 FastPix
3+
Copyright (c) 2025 FastPix Inc
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,31 @@ With the FastPix iOS Player SDK, you can configure resolution selection prioriti
170170
playerViewController.prepare(playbackID: playbackID, playbackOptions: PlaybackOptions(minResolution : (example : .atLeast270p) ,maxResolution : (example : .upTo1080p ,renditionOrder: .descending ))
171171
```
172172

173+
## DRM Support:
174+
175+
FastPixPlayer supports DRM-encrypted playback using FairPlay.
176+
To enable DRM, follow the guide below and include token (playback token), drm-token (DRM license JWT), licenseURL (DRM license server URL), and certificateURL (FairPlay application certificate URL) as attributes.
177+
178+
[Secure Playback with DRM – FastPix Documentation](https://docs.fastpix.io/docs/secure-playback-with-drm#/)
179+
180+
```swift
181+
182+
// play DRM-encrypted playback
183+
184+
if let token = self.playbackToken {
185+
let licenseURL = URL(string: "https://api.fastpix.io/v1/on-demand/drm/license/fairplay/\(self.playbackID)?token=\(token)")!
186+
let certificateURL = URL(string: "https://api.fastpix.io/v1/on-demand/drm/cert/fairplay/\(self.playbackID)?token=\(token)")!
187+
188+
playerViewController.prepare(
189+
playbackID: playbackID,
190+
playbackOptions: PlaybackOptions(
191+
playbackToken: token,
192+
drmOptions: DRMOptions(licenseURL: licenseURL, certificateURL: certificateURL)
193+
)
194+
)
195+
}
196+
```
197+
173198
#### Each of these features is designed to enhance both flexibility and user experience, providing complete control over video playback, appearance, and user interactions in FastPix-player.
174199

175200
# Supporting tvOS
@@ -233,6 +258,3 @@ class TVPlayerViewController: UIViewController {
233258
## Maturity
234259

235260
This SDK is currently in beta, and breaking changes may occur between versions even without a major version update. To avoid unexpected issues, we recommend pinning your dependency to a specific version. This ensures consistent behavior unless you intentionally update to a newer release.
236-
237-
238-

Sources/FastPixPlayerSDK/FastPixAvPlayerController.swift

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
//
2-
// FastPixAvPlyaerController.swift
3-
//
4-
//
5-
// Created by Neha Reddy on 16/06/24.
6-
//
71
import Foundation
82
import AVKit
93

@@ -16,6 +10,11 @@ extension AVPlayerViewController {
1610
public convenience init(playbackID: String) {
1711
self.init()
1812

13+
guard !playbackID.isEmpty else {
14+
print("Error: Empty playback ID")
15+
return
16+
}
17+
1918
let playerItem = AVPlayerItem(playbackID: playbackID)
2019

2120
let player = AVPlayer(playerItem: playerItem)
@@ -33,13 +32,31 @@ extension AVPlayerViewController {
3332
public convenience init(playbackID: String,playbackOptions: PlaybackOptions) {
3433
self.init()
3534

36-
let playerItem = AVPlayerItem(
37-
playbackID: playbackID,
38-
playbackOptions: playbackOptions
39-
)
35+
// Validate playbackID
36+
guard !playbackID.isEmpty else {
37+
print("Error: Empty playback ID")
38+
return
39+
}
40+
41+
let playerItem: AVPlayerItem
42+
43+
if let drmOptions = playbackOptions.drmOptions {
44+
// DRM-enabled playback
45+
playerItem = AVPlayerItem(
46+
playbackID: playbackID,
47+
playbackOptions: playbackOptions,
48+
licenseServerUrl: drmOptions.licenseURL,
49+
certificateUrl: drmOptions.certificateURL
50+
)
51+
} else {
52+
// Regular playback
53+
playerItem = AVPlayerItem(
54+
playbackID: playbackID,
55+
playbackOptions: playbackOptions
56+
)
57+
}
4058

4159
let player = AVPlayer(playerItem: playerItem)
42-
print(playerItem)
4360
self.player = player
4461
}
4562

@@ -65,12 +82,24 @@ extension AVPlayerViewController {
6582
/// - playbackOptions: playback-related options such
6683
/// as custom domain and maximum resolution
6784
public func prepare(playbackID: String,playbackOptions: PlaybackOptions) {
68-
prepare(
69-
playerItem: AVPlayerItem(
85+
86+
let playerItem: AVPlayerItem
87+
88+
if let drmOptions = playbackOptions.drmOptions {
89+
playerItem = AVPlayerItem(
90+
playbackID: playbackID,
91+
playbackOptions: playbackOptions,
92+
licenseServerUrl: drmOptions.licenseURL,
93+
certificateUrl: drmOptions.certificateURL
94+
)
95+
} else {
96+
playerItem = AVPlayerItem(
7097
playbackID: playbackID,
7198
playbackOptions: playbackOptions
7299
)
73-
)
100+
}
101+
102+
prepare(playerItem: playerItem)
74103
}
75104

76105
internal func prepare(playerItem: AVPlayerItem) {
@@ -85,4 +114,3 @@ extension AVPlayerViewController {
85114
}
86115
}
87116
}
88-

Sources/FastPixPlayerSDK/FastPixAvPlayerItem.swift

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
//
2-
// FastPixAvPlayerItem.swift
3-
//
4-
//
5-
// Created by Neha Reddy on 16/06/24.
6-
//
7-
81
import AVFoundation
92
import Foundation
3+
import ObjectiveC.runtime
104

115
internal extension URLComponents {
126
init(playbackID: String,playbackOptions: PlaybackOptions) {
@@ -56,7 +50,7 @@ internal extension URLComponents {
5650
)
5751
self.queryItems = queryItems
5852
}
59-
53+
6054
}
6155
}
6256

@@ -92,7 +86,7 @@ fileprivate func createPlaybackURL(playbackID: String,playbackOptions: PlaybackO
9286
)
9387
)
9488
}
95-
89+
9690
if playbackOptions.maxResolution != .standard, playbackOptions.maxResolution != nil {
9791
queryItems.append(
9892
URLQueryItem(
@@ -133,11 +127,72 @@ fileprivate func createPlaybackURL(playbackID: String,playbackOptions: PlaybackO
133127
guard let playbackURL = components.url else {
134128
preconditionFailure("Invalid playback URL components")
135129
}
136-
// print("---->",components.url?.absoluteString)
137-
print("---->",playbackURL)
138130
return playbackURL
139131
}
140132

133+
// MARK: - DRM Delegate
134+
public class FastPixDRMDelegate: NSObject, AVAssetResourceLoaderDelegate {
135+
136+
private let licenseServerUrl: URL
137+
private let certificateUrl: URL
138+
139+
init(licenseServerUrl: URL, certificateUrl: URL) {
140+
self.licenseServerUrl = licenseServerUrl
141+
self.certificateUrl = certificateUrl
142+
}
143+
144+
@available(tvOS 13.0.0, *)
145+
@available(iOS 13.0.0, *)
146+
private func fetchCertificate() async throws -> Data {
147+
let (data, _) = try await URLSession.shared.data(from: certificateUrl)
148+
return data
149+
}
150+
151+
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader,
152+
shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
153+
154+
guard let url = loadingRequest.request.url, url.scheme == "skd" else {
155+
return false
156+
}
157+
158+
if #available(iOS 13.0, *) {
159+
if #available(tvOS 13.0, *) {
160+
Task {
161+
do {
162+
let certificate = try await fetchCertificate()
163+
guard let contentIdData = url.host?.data(using: .utf8) else {
164+
loadingRequest.finishLoading(with: NSError(domain: "FastPixDRM", code: -1))
165+
return
166+
}
167+
168+
let spcData = try loadingRequest.streamingContentKeyRequestData(
169+
forApp: certificate,
170+
contentIdentifier: contentIdData,
171+
options: nil
172+
)
173+
var request = URLRequest(url: licenseServerUrl)
174+
request.httpMethod = "POST"
175+
request.httpBody = spcData
176+
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
177+
178+
let (ckcData, _) = try await URLSession.shared.data(for: request)
179+
loadingRequest.dataRequest?.respond(with: ckcData)
180+
loadingRequest.finishLoading()
181+
} catch {
182+
loadingRequest.finishLoading(with: error)
183+
}
184+
}
185+
} else {
186+
// Fallback on earlier versions
187+
}
188+
} else {
189+
// Fallback on earlier versions
190+
}
191+
192+
return true
193+
}
194+
}
195+
141196
internal extension AVPlayerItem {
142197

143198
// Initializes a player item with a playback URL that
@@ -150,7 +205,6 @@ internal extension AVPlayerItem {
150205
//
151206
// - Parameter playbackID: playback ID of the FastPix Asset
152207
convenience init(playbackID: String) {
153-
// self.init(playbackID: playbackID)
154208

155209
let defaultOptions = PlaybackOptions() // Ensure this struct has default values
156210

@@ -160,7 +214,7 @@ internal extension AVPlayerItem {
160214
)
161215
self.init(url: playbackURL)
162216
}
163-
217+
164218
// Initializes a player item with a playback URL that
165219
// references your FastPix Video at the supplied playback ID.
166220
// The playback ID must be public.
@@ -175,4 +229,22 @@ internal extension AVPlayerItem {
175229
)
176230
self.init(url: playbackURL)
177231
}
232+
233+
234+
// DRM initializer
235+
convenience init(playbackID: String,
236+
playbackOptions: PlaybackOptions,
237+
licenseServerUrl: URL,
238+
certificateUrl: URL) {
239+
let playbackURL = createPlaybackURL(
240+
playbackID: playbackID,
241+
playbackOptions: playbackOptions
242+
)
243+
let asset = AVURLAsset(url: playbackURL)
244+
let delegate = FastPixDRMDelegate(licenseServerUrl: licenseServerUrl,
245+
certificateUrl: certificateUrl)
246+
asset.resourceLoader.setDelegate(delegate, queue: DispatchQueue.global(qos: .userInitiated))
247+
self.init(asset: asset)
248+
objc_setAssociatedObject(self, "FastPixDRMDelegateKey", delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
249+
}
178250
}

Sources/FastPixPlayerSDK/FastPixAvPlayerLayer.swift

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
//
2-
// File.swift
3-
//
4-
//
5-
// Created by Neha Reddy on 21/07/24.
6-
//
7-
81
import AVFoundation
92
import Foundation
103

@@ -32,15 +25,26 @@ extension AVPlayerLayer {
3225
/// - playbackOptions: playback-related options such
3326
/// as custom domain and maximum resolution
3427
public convenience init(playbackID: String,playbackOptions: PlaybackOptions) {
28+
3529
self.init()
3630

37-
let playerItem = AVPlayerItem(
38-
playbackID: playbackID,
39-
playbackOptions: playbackOptions
40-
)
31+
let playerItem: AVPlayerItem
32+
33+
if let drmOptions = playbackOptions.drmOptions {
34+
playerItem = AVPlayerItem(
35+
playbackID: playbackID,
36+
playbackOptions: playbackOptions,
37+
licenseServerUrl: drmOptions.licenseURL,
38+
certificateUrl: drmOptions.certificateURL
39+
)
40+
} else {
41+
playerItem = AVPlayerItem(
42+
playbackID: playbackID,
43+
playbackOptions: playbackOptions
44+
)
45+
}
4146

4247
let player = AVPlayer(playerItem: playerItem)
43-
print(playerItem)
4448
self.player = player
4549
}
4650

@@ -66,12 +70,24 @@ extension AVPlayerLayer {
6670
/// - playbackOptions: playback-related options such
6771
/// as custom domain and maximum resolution
6872
public func prepare(playbackID: String,playbackOptions: PlaybackOptions) {
69-
prepare(
70-
playerItem: AVPlayerItem(
73+
74+
let playerItem: AVPlayerItem
75+
76+
if let drmOptions = playbackOptions.drmOptions {
77+
playerItem = AVPlayerItem(
78+
playbackID: playbackID,
79+
playbackOptions: playbackOptions,
80+
licenseServerUrl: drmOptions.licenseURL,
81+
certificateUrl: drmOptions.certificateURL
82+
)
83+
} else {
84+
playerItem = AVPlayerItem(
7185
playbackID: playbackID,
7286
playbackOptions: playbackOptions
7387
)
74-
)
88+
}
89+
90+
prepare(playerItem: playerItem)
7591
}
7692

7793
internal func prepare(playerItem: AVPlayerItem) {

0 commit comments

Comments
 (0)