Воспроизведение зашифрованного контента Apple Fairplay на iOS 26IOS

Программируем под IOS
Ответить
Anonymous
 Воспроизведение зашифрованного контента Apple Fairplay на iOS 26

Сообщение Anonymous »

Для iOS17 у нас не было проблем с воспроизведением зашифрованного контента Apple Fairplay с ключами, полученными с нашего сервера ключей, работающего на FairPlay Streaming Server SDK 5.1, а затем на FairPlay Streaming Server SDK 26. Он был создан и развернут с использованием **Xcode версии 26.1.**1 (17B100) без каких-либо изменений в коде, и, как и ожидалось, контент продолжал успешно расшифровываться и воспроизводиться (пока все хорошо). Однако как только устройство будет обновлено до iOS26, оно больше не будет воспроизводить зашифрованный контент.
Устройства, оставшиеся на iOS17, продолжают работать нормально, и журналы отладки представляют собой проверку работоспособности, подтверждающую это. Кто-нибудь еще испытывает эту проблему? Мы несколько раз поднимали этот вопрос перед Apple за последние два месяца и до сих пор не получили ответа от их инженеров или форума разработчиков.
Вот код (вы должны иметь возможность добавить его в новый проект iOS Xcode и предоставить URL-адрес сервера, URL-адрес контента и сертификат). Заранее большое спасибо и тем, кто уже ответил на мою неудачную первую попытку обратиться за помощью.
В iOS26 возвращается ошибка
> signalled err=-12860 at :1522 > signalled err=-12860 at :1522 > signalled err=-12860 at :1522

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()
}
}


Подробнее здесь: https://stackoverflow.com/questions/798 ... -on-ios-26
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»