Wiedergabe von mit Apple Fairplay verschlüsselten Inhalten auf iOS 26IOS

Programmierung für iOS
Anonymous
 Wiedergabe von mit Apple Fairplay verschlüsselten Inhalten auf iOS 26

Post by Anonymous »

Für iOS17 hatten wir keine Probleme beim Abspielen von Apple Fairplay-verschlüsselten Inhalten mit Schlüsseln, die von unserem Schlüsselserver geliefert wurden, der auf FairPlay Streaming Server SDK 5.1 und anschließend auf FairPlay Streaming Server SDK 26 läuft. Es wurde mit **Xcode Version 26.1.**1 (17B100) ohne Änderungen am Code erstellt und bereitgestellt und – wie erwartet – wurde der Inhalt weiterhin erfolgreich entschlüsselt und abgespielt (soweit so gut). Sobald ein Gerät jedoch auf iOS26 aktualisiert wurde, spielte dieses Gerät den verschlüsselten Inhalt nicht mehr ab.
Geräte, die auf iOS17 verblieben sind, funktionieren weiterhin normal und die Debugging-Protokolle sind eine Plausibilitätsprüfung, die dies beweist. Hat noch jemand dieses Problem? Wir haben dieses Problem in den letzten zwei Monaten mehrmals bei Apple angesprochen und bisher noch keine Antwort von den Ingenieuren oder dem Entwicklerforum erhalten.
Hier ist der Code (Sie sollten ihn in ein neues iOS-Xcode-Projekt einfügen und eine Server-URL, eine Inhalts-URL und ein Zertifikat bereitstellen können). Vielen Dank im Voraus und an diejenigen, die bereits auf meinen schlechten ersten Versuch, um Hilfe zu bitten, geantwortet haben.
Der auf iOS26 zurückgegebene Fehler ist

Code: Select all

> signalled err=-12860 at :1522 > signalled err=-12860 at :1522 >  signalled err=-12860 at :1522

Code: Select all

import UIKit
import AVFoundation

class ViewController: UIViewController {
private var player: AVPlayer?
private var keyDelegate: ContentKeyDelegate?

override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .orange
self.configureAudioSession()

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate
, let hlsURL = appDelegate.hlsURL()
, let certificateData = appDelegate.certificate()
, let licenseServerURL = appDelegate.licenseServerURL() else {
return
}

let delegate = ContentKeyDelegate(appCertificate: certificateData,licenseServerURL: licenseServerURL)
self.keyDelegate = delegate

let asset = AVURLAsset(url: hlsURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: NSNumber(value: false)
])

delegate.setAsset(asset)

let item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)
player.automaticallyWaitsToMinimizeStalling = true
player.allowsExternalPlayback = false
player.preventsDisplaySleepDuringVideoPlayback = true
self.player = player

let layer = AVPlayerLayer(player: player)
layer.frame = view.bounds
layer.videoGravity = .resizeAspect
self.view.layer.addSublayer(layer)
player.play()
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let playerLayer = view.layer.sublayers?.first(where: { $0 is AVPlayerLayer }) as? AVPlayerLayer {
playerLayer.frame = view.bounds
}
}

private func configureAudioSession() {
do {
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback, mode: .moviePlayback, options: [])
try audioSession.setActive(true)
print("✅ Audio session configured successfully")
} catch {
print("❌ Failed to configure audio session: \(error)")
}
}
}

final class ContentKeyDelegate: NSObject, AVContentKeySessionDelegate {
private let appCertificate: Data
private let licenseServerURL: URL
private let keySession: AVContentKeySession

init(appCertificate: Data, licenseServerURL: URL) {
self.appCertificate = appCertificate
self.licenseServerURL = licenseServerURL
self.keySession = AVContentKeySession(keySystem: .fairPlayStreaming)
super.init()

self.keySession.setDelegate(self, queue: DispatchQueue.main)
}

func setAsset(_ asset: AVURLAsset) {
self.keySession.addContentKeyRecipient(asset)
}

func contentKeySession(_ session: AVContentKeySession, didProvide keyRequest: AVContentKeyRequest) {
process(keyRequest: keyRequest)
}

func contentKeySession(_ session: AVContentKeySession, didProvideRenewingContentKeyRequest keyRequest: AVContentKeyRequest) {
process(keyRequest: keyRequest)
}

private func process(keyRequest: AVContentKeyRequest) {
guard let assetIDString = keyRequest.identifier as? String, let assetIDData = assetIDString.data(using: .utf8) else {
keyRequest.processContentKeyResponseError(NSError(domain: "ContentKey", code: -1))
return
}

Task {
do {
let spcData = try await keyRequest.makeStreamingContentKeyRequestData(forApp: appCertificate,contentIdentifier: assetIDData, options: nil)
self.fetchCKC(spcData: spcData, assetId: assetIDString) { result in
switch result {
case .success(let ckcOpt):
guard let ckcData = ckcOpt else {
DispatchQueue.main.async {
keyRequest.processContentKeyResponseError(ProgramError.noCKCReturnedByKSM)
}
return
}

let response = AVContentKeyResponse(fairPlayStreamingKeyResponseData:  ckcData)
DispatchQueue.main.async {
keyRequest.processContentKeyResponse(response)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
if let err = keyRequest.error {
Swift.print("keyRequest.error: \(err.localizedDescription) / \(err)")
} else {
Swift.print("keyRequest.error: nil")
}
}
}

case .failure(let error):
Swift.print("fetchCKC error: \(error)")
DispatchQueue.main.async {
keyRequest.processContentKeyResponseError(error)
}
}
}

} catch {
Swift.print("SPC generation error: \(error)")
DispatchQueue.main.async {
keyRequest.processContentKeyResponseError(error)
}
}
}
}

private func fetchCKC(spcData: Data, assetId: String, completion: @escaping (Result) ->  Void) {
if let contentKeyData = Data(base64Encoded: "X7sBORoVsqx/M96GnKLUqA==")
, let initialisationVectorData = Data(base64Encoded: "beXQ7IjxPAxSk29JU3MgvA==") {
let endpoint = "https://fairplay-key-server.compilerdefault.com/fps/"
if let url = URL(string: endpoint) {
let spcBase64 = spcData.base64EncodedString()
let contentKeyHex = ContentKeyDelegate.hex(forData: contentKeyData)
let initialisationVectorHex = ContentKeyDelegate.hex(forData: initialisationVectorData)

let parameters: [String: Any] = [
"fairplay-streaming-request": [
"version": 1,
"create-ckc": [[
"id": 1,
"asset-info": [[
"content-key": contentKeyHex,
"content-iv": initialisationVectorHex,
"encryption-scheme": "cbcs",
"hdcp-type": 0,
"content-type": "hd",
"lease-duration": 1200
]],
"spc": spcBase64
]]
]
]

do {
let postData = try JSONSerialization.data(withJSONObject: parameters)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = postData

let task = URLSession.shared.dataTask(with: request) {data, response, error in
if let response = response as? HTTPURLResponse {
Swift.print("KSM status code: \(response.statusCode)")
}

if let error = error {
Swift.print("KSM request error: \(error)")
completion(.failure(error))
return
}

guard let data = data, var responseString = String(data: data, encoding: .utf8) else {
Swift.print("KSM response empty or not UTF-8")
completion(.failure(ProgramError.noCKCReturnedByKSM))
return
}

if responseString.hasPrefix("Result") {
responseString.removeFirst("Result".count)
}

guard let jsonData = responseString.data(using: .utf8) else {
Swift.print("Failed to convert trimmed response to data")
completion(.failure(ProgramError.noCKCReturnedByKSM))
return
}

do {
if let dictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
, let fairplayStreamingResponse = dictionary["fairplay-streaming-response"] as? [String: Any]
, let ckcArray = fairplayStreamingResponse["create-ckc"] as? [[String: Any]]
, let ckcDictionary = ckcArray.first
, let ckc = ckcDictionary["ckc"] as? String
, let thatData = Data(base64Encoded: ckc) {
completion(.success(thatData))
} else {
completion(.failure(ProgramError.noCKCReturnedByKSM))
}
} catch {
Swift.print("JSON parse error:  \(error)")
completion(.failure(error))
}
}
task.resume()
} catch let error {
Swift.print(error)
completion(.failure(error))
}
} else {
let error = NSError()
completion(.failure(error))
}
}
}

enum ProgramError: Error {
case missingApplicationCertificate
case noCKCReturnedByKSM
}

func contentKeySession(_ session: AVContentKeySession, didFailWithError error: Error) {
Swift.print("AVContentKeySession didFailWithError: \(error.localizedDescription) — \(error)")
}

public static func hex(forData theData: Data) -> String {
return theData.map { String(format: "%02hhx", $0) }.joined()
}
}

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post