I'm attempting to integrate DRM into the app. I've developed a prototype, but the delegate method shouldWaitForLoadingOfRequestedResource isn't being triggered on certain devices, although it functions correctly on others. Notably, it's invoked on Apple TV 4K (3rd generation) Wi-Fi (A2737) but not on Apple TV HD (A1625). Are there any specific configurations needed to ensure this method is invoked?
let url = URL(string: RESOURCE_URL)!
// Create the asset instance and the resource loader because we will be asked
// for the license to playback DRM protected asset.
let asset = AVURLAsset(url: url)
let queue = DispatchQueue(label: CUSTOM_SERIAL_QUEUE_LABEL)
asset.resourceLoader.setDelegate(self, queue: queue)
// Create the player item and the player to play it back in.
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
}
//Please note if your delegate method is not being called then you need to run on a REAL DEVICE
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
// Getting data for KSM server. Get the URL from tha manifest, we wil need it later as it
// contains the assetId required for the license request.
guard let url = loadingRequest.request.url else {
print(#function, "Unable to read URL from loadingRequest")
loadingRequest.finishLoading(with: NSError(domain: "", code: -1, userInfo: nil))
return false
}
// Link to your certificate on BuyDRM's side.
// Use the commented section if you want to refer the certificate from your bundle i.e. Store Locally
/*
guard let certificateURL = Bundle.main.url(forResource: "certificate", withExtension: "der"), let certificateData = try? Data(contentsOf: certificateURL) else {
print("failed...", #function, "Unable to read the certificate data.")
loadingRequest.finishLoading(with: NSError(domain: "com.domain.error", code: -2, userInfo: nil))
return false
}
*/
guard let certificateData = try? Data(contentsOf: URL(string: CERTIFICATE_URL)!) else {
print(#function, "Unable to read the certificate data.")
loadingRequest.finishLoading(with: NSError(domain: "", code: -2, userInfo: nil))
return false
}
// The assetId from the main/variant manifest - skd://***, the *** part. Get the SPC based on the
// already collected data i.e. certificate and the assetId
guard let contentId = url.host, let contentIdData = contentId.data(using: String.Encoding.utf8) else {
loadingRequest.finishLoading(with: NSError(domain: "", code: -3, userInfo: nil))
print(#function, "Unable to read the SPC data.")
return false
}
guard
let spcData = try? loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: nil) else {
loadingRequest.finishLoading(with: NSError(domain: "", code: -3, userInfo: nil))
print(#function, "Unable to read the SPC data.")
return false
}
// Prepare to get the license i.e. CKC.
let requestUrl = CKC_URL
let stringBody = "spc=\(spcData.base64EncodedString())&assetId=\(contentId)"
let postData = NSData(data: stringBody.data(using: String.Encoding.utf8)!)
// Make the POST request with customdata set to the authentication XML.
var request = URLRequest(url: URL(string: requestUrl)!)
request.httpMethod = "POST"
request.httpBody = postData as Data
request.allHTTPHeaderFields = ["customdata" : ACCESS_TOKEN]
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let task = session.dataTask(with: request) { data, response, error in
if let data = data {
// The response from the KeyOS MultiKey License server may be an error inside JSON.
do {
let parsedData = try JSONSerialization.jsonObject(with: data) as! [String:Any]
let errorId = parsedData["errorid"] as! String
let errorMsg = parsedData["errormsg"] as! String
print(#function, "License request failed with an error: \(errorMsg) [\(errorId)]")
} catch let error as NSError {
print(#function, "The response may be a license. Moving on.", error)
}
// The response from the KeyOS MultiKey License server is Base64 encoded.
let dataRequest = loadingRequest.dataRequest!
// This command sends the CKC to the player.
dataRequest.respond(with: Data(base64Encoded: data)!)
loadingRequest.finishLoading()
} else {
print(#function, error?.localizedDescription ?? "Error during CKC request.")
}
}
task.resume()
// Tell the AVPlayer instance to wait. We are working on getting what it wants.
return true
}
FairPlay Streaming
RSS for tagSecurely deliver streaming media to devices through the HTTP Live Streaming protocol using FairPlay Streaming (FPS).
Posts under FairPlay Streaming tag
37 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi guys, I'm implementing FairPlay support for a video streaming application. I've managed to get as far as generating the SPC and acquiring a license from the license server. However when it comes to parsing the license (CKC) returned from the server, the FPS module returns error code -42671. Has anyone else faced this before and / or knows what the fix is? I thought passing it the license should be enough unless additional data is required?
Dear Apple Engineers,
I have downloaded the FairPlay Streaming SDK 4.4 - In which i could able to make use of the fps_safari_hls_example.html file to make a successful playback of fairplay protected content in safari, by pointing our fairplay license server, .m3u8 file & certificate.
Now that, i'm trying to achieve the renewal concept, so i tried to use the fps_safari_hls_key_renewal.html file also setting up the ContentKeyDuration to 20sec in FP license server backend. But client didn't make any subsequent FP license request around when nearing 20th sec or post 20th sec. I wonder if this use case be achieved in safari,
the only extra functionality i could see in renewal html file is below piece of code
await runAndWaitForLicenseRequest(session, keyURI, () => {
session.update(stringToUInt8Array("renew"))
});
Based on the above piece, i assume that we are making sure that client to aware the when to renew the license. But in my case, there were no FP request is made , in-fact, this piece of code got executed immediate after the 1st FP license request call & protected media continues to play despite setting the ContentKeyDuration to 20sec with LIMITED as contentkeyType .
Could you please suggest on how to achieve the subsequent renewal request from client based on the ContentKeyDuration send in the CKC response using this sample renewal html file..? Is there any tewaks to be made in html file, kindly suggest.
Hello there,
I understand most components of the FairPlay Streaming Server SDK haven't been updated in about a decade, but the verify_ckc tool no longer works, and it's really helpful. It throws errors when trying to import the Crypto library...probably something to do with this whole Python 2 vs Python 3 thing.
I don't know anything about Python or I'd try to fix it myself but...I'm just bringing this up in case anyone from the FairPlay team reads these things!
Thx
Hi
I am trying to implement D Function in python.
import hashlib
from Crypto.Cipher import AES
from Crypto.Hash import SHA
from Crypto.Util.Padding import pad as padding
from Crypto.PublicKey import RSA
PRIME = 813416437
NB_RD = 16
def dfunction(R2, ASk):
tmp = bytearray(20)
pad = bytearray(64)
MBlock = [0] * 14
P = PRIME
if len(R2) >= 56:
return -1 # kDRMSKDServerParamErr
# Padding until a multiple of 56B
pad[:len(R2)] = R2
pad[len(R2)] = 0x80
pad[len(R2) + 1:56] = [0] * (56 - len(R2) - 1)
# Create 14 32b values
for i in range(14):
MBlock[i] = (pad[4 * i] << 24) ^ (pad[4 * i + 1] << 16) ^ (pad[4 * i + 2] << 8) ^ pad[4 * i + 3]
# Reunify into 2 32 bits values
#MBlock[0] += sum(MBlock[1:7])
#MBlock[1] = 0
#MBlock[1] += sum(MBlock[7:])
# Reunify into 2 32 bits values
for i in range(1, 7):
MBlock[0] += MBlock[i]
MBlock[1] = 0
for i in range(7):
MBlock[1] += MBlock[i + 7]
# Apply the function (C_r)
for i in range(2):
for r in range(NB_RD):
if MBlock[i] & 1:
MBlock[i] >>= 1
else:
MBlock[i] = (3 * MBlock[i] + 1) % P
# Append to M
for i in range(4):
pad[56 + i] = (MBlock[0] >> (8 * i)) & 0xFF
pad[60 + i] = (MBlock[1] >> (8 * i)) & 0xFF
h = hashlib.sha1()
h.update(pad)
hh = h.digest()[:16]
cipher = AES.new(ASk, AES.MODE_ECB)
DASk = cipher.encrypt(padding(hh, AES.block_size))
return DASk
Then, i am trying to match integrities and they does not match. I think there is an mistake in D Function implementation. Any clue whats wrong?
Thank you
Hi
I was wondering if there any D Function implementations on Python to derive dASK?
Thank you
We are aiming to apply KeyRotation for FPS service for our content streaming service.
It is not clear from the spécifications how "FairPlay Streaming Programming Guide.pdf" How to apply this feature and let the "FPS DRM key server KSM " send multiple-keys at a time in one licence :(CKC) in the key rotation moment. without using the Renting and Leasing key features.
Note that we don't use Offline Playback capabilities for our streams.
Hello,
I keep getting this kind of errors (specifically 16247) when trying to download DRM-protected HLS content, would anyone have a clue about the reason why ?
Error Domain=CoreMediaErrorDomain Code=-16247 "(null)" UserInfo={_NSURLErrorRelatedURLSessionTaskErrorKey=("BackgroundAVAssetDownloadTask
Thanks
Sylvain
how to use AIGC in the workplace to assist with work?
Hello everyone,
I was playing a livestream when I received the error -16831/START-TIME is too close to live returned from the AVPlayerItemNewErrorLogEntry function. I don't know why the error is returned, please help me explain.
Similarly, I am also getting error: -12888/Playlist File unchanged for longer than 1.5 * target duration , I also read error -12888 in the documentation page 170: https://docs.huihoo.com/apple/wwdc/ 2018/502_measuring_and_optimizing_hls_performance.pdf
but still don't understand the reason.
Hope you can help!
Per FP Streaming programming guide,
The SPC includes a specific TLLV to provide the state of the media content playback. And total value length of this is 16 in decimals.
Here i'm trying to retrieve the Playback State. which is of 20-23 ByteRange.
byte[] mediaPlaybackStateBlock = getBlock(MEDIA_PLAYBACK_STATE).getValueData();
playbackState = Arrays.copyOfRange(mediaPlaybackStateBlock, 20, 24);
I'm endup in issue - arraycopy: length -4 is negative.
I'm bit confused on how to retrieve the playback state from 20-23 ByteRange when its length jus 16..
Kindly clarify
Hello all,
I am a completely new to iOS development, and I need to make an app that will be playing encrypted content using Fairplay. Right now I am using the Sample client in the latest FPS Server SDK (HLSCatalog), and from what I understand the default sources in Streams.plist are not protected. There's an entry with is_protected=YES and where I can replace the playlist_url, but I was wondering if I could use the .m3u8 files in the FPS Test Content found at https://developer.apple.com/streaming/fps/ ? If so, I don't really know how to.
In one of the .m3u8 files of the Test Content, I could find the "content_key_id_list" example value that's also mentioned in the client's README (skd://twelve), which is why I'm asking.
Thanks in advance!
Hi Team,
We see an issue with this version if CoreMedia requesting multiple qualities at all times for a stream. We don't see this issue on 1.0.0.21C62. We are unsure what would be causing this.
[2024-01-05 16:53:51] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=630&_HLS_part=0 HTTP/1.0" 200 1145 2529 1090199 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:52] "GET /live/cinecanal/live/cinecanal_1080p/audio.m3u8?_HLS_msn=630&_HLS_part=0 HTTP/1.0" 200 1146 2396 1013356 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:52] "GET /live/cinecanal/live/cinecanal_1080p/a_1459_2452140264_630_0.fmp4 HTTP/1.0" 200 1139 24975 1013385 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:52] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=630&_HLS_part=1 HTTP/1.0" 200 1145 2603 998670 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:52] "GET /live/cinecanal/live/cinecanal_720p/v_1058_2452140000_630_1.fmp4 HTTP/1.0" 200 1138 40534 998739 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:53] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=630&_HLS_part=2 HTTP/1.0" 200 1145 2677 835327 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:53] "GET /live/cinecanal/live/cinecanal_720p/v_1058_2452140000_630_2.fmp4 HTTP/1.0" 200 1138 57656 835207 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:53] "GET /live/cinecanal/live/cinecanal_1080p/audio.m3u8?_HLS_msn=630&_HLS_part=1 HTTP/1.0" 200 1146 2458 986038 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:53] "GET /live/cinecanal/live/cinecanal_1080p/a_1459_2452140264_630_1.fmp4 HTTP/1.0" 200 1139 24700 986032 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:54] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=630&_HLS_part=3 HTTP/1.0" 200 1145 2751 1013257 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:54] "GET /live/cinecanal/live/cinecanal_720p/v_1058_2452140000_630_3.fmp4 HTTP/1.0" 200 1138 55900 1013324 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:54] "GET /live/cinecanal/live/cinecanal_1080p/audio.m3u8?_HLS_msn=630&_HLS_part=2 HTTP/1.0" 200 1146 2520 1016693 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:54] "GET /live/cinecanal/live/cinecanal_1080p/a_1459_2452140264_630_2.fmp4 HTTP/1.0" 200 1139 25014 1016717 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:55] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=630&_HLS_part=4 HTTP/1.0" 200 1145 2825 917753 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:55] "GET /live/cinecanal/live/cinecanal_720p/v_1058_2452140000_630_4.fmp4 HTTP/1.0" 200 1138 103745 917903 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:55] "GET /live/cinecanal/live/cinecanal_1080p/audio.m3u8?_HLS_msn=630&_HLS_part=3 HTTP/1.0" 200 1146 2582 958102 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:55] "GET /live/cinecanal/live/cinecanal_1080p/a_1459_2452140264_630_3.fmp4 HTTP/1.0" 200 1139 24782 958195 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:56] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=630&_HLS_part=5 HTTP/1.0" 200 1145 2899 931101 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:56] "GET /live/cinecanal/live/cinecanal_720p/v_1058_2452140000_630_5.fmp4 HTTP/1.0" 200 1138 112113 931228 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:56] "GET /live/cinecanal/live/cinecanal_1080p/audio.m3u8?_HLS_msn=630&_HLS_part=4 HTTP/1.0" 200 1146 2644 935550 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:56] "GET /live/cinecanal/live/cinecanal_1080p/a_1459_2452140264_630_4.fmp4 HTTP/1.0" 200 1139 24824 937720 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:57] "GET /live/cinecanal/live/cinecanal_1080p/audio.m3u8?_HLS_msn=630&_HLS_part=5 HTTP/1.0" 200 1146 2706 895680 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:57] "GET /live/cinecanal/live/cinecanal_1080p/a_1459_2452140264_630_5.fmp4 HTTP/1.0" 200 1139 24843 895734 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
[2024-01-05 16:53:57] "GET /live/cinecanal/live/cinecanal_720p/video.m3u8?_HLS_msn=631&_HLS_part=0 HTTP/1.0" 200 1145 2529 907045 "-" "AppleCoreMedia/1.0.0.21B101 (iPhone; U; CPU OS 17_1_2 like Mac OS X; en_us)"
The error shown in the picture appears when I try to link the StartScene.sks file to the code
Attached are pictures of the code
Please help me🙏🏻
The error shown in the picture appears when I try to link the StartScene.sks file to the code
Attached are pictures of the code
Please help me🙏🏻
We have to play some encrypted videos from server.
In AVAssetResourceLoaderDelegate we got the ckc data correctly and responded with that. Then video just starts playing and stops immdediately. When we check the playerItem error description we got Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action" UserInfo={NSLocalizedDescription=Cannot Complete Action, NSLocalizedRecoverySuggestion=Try again later.}. Any one encountered this?
Hi Apple Team, we are observing following error intermittently when trying to playback FairPlay protected HLS streams. The error happens immediately after loading the certificate.
Playback with same certificate on same device(Mac, iPhone) works most of time but intermittently this error is observed with following codes. The code=6 means MEDIA_KEYERR_DOMAIN but I did not find any information on what does systemCode=4294955417 mean? Is there a way to check what does this system code mean and what could be causing this intermittent behaviour?
{
"code": 6,
"systemCode": 4294955417
}