如何在 swift/ios 的 AVPlayer 中播放受 DRM 保护的视频?

How to play DRM protected videos in AVPlayer in swift/ios?

提问人:zaid afzal 提问时间:5/4/2023 最后编辑:zaid afzal 更新时间:5/31/2023 访问量:703

问:

这是我从后端 api 获得的响应下方。 如何在带有 widevine 和没有 widevine 的 swift AVPlayer 中播放扩展名为 .mpd 的视频。
我的证书数据现在正在下载中,但我无法获取 streamingContentKeyRequestData。
我正在关注这篇中等文章。
https://medium.com/@burak.oguz/ios-fairplay-drm-integration-with-different-use-cases-8aff3d4248dd 响应 JSON

{
  "url": "https://z2cltd.akamaized.net/example-4671-mp4-cbcs.mpd",
  "fairPlayCertificateUrl": "https://mw.example.net/static/FairPlay.der",

   "drms": {
            "com.widevine.alpha": "https://mw.example.net/widevine_proxy",
            "com.apple.fps": "https://mw.example.net/ksm",
            "com.apple.fps.1_0": "https://mw.example.net/ksm"
        },
}

ViewController的:

class ViewController: UIViewController {
    
    var player: AVPlayer?
    var asset: AVURLAsset?

    override func viewDidLoad() {
        super.viewDidLoad()
       
        // Create the AVURLAsset and assign the delegate
              guard let url = URL(string: "htps://z2cltd.akamaized.net/mini01/vods/49/s1e1-andhera-ujala-ghani-chaon-copy-3395/64366c9ccc181-4606-mp4-cbcs.mpd") else {
                  return
              }

              asset = AVURLAsset(url: url)
              asset?.resourceLoader.setDelegate(self, queue: DispatchQueue.global(qos: .default))

              // Create an AVPlayerItem using the AVURLAsset
              let playerItem = AVPlayerItem(asset: asset!)
//
              // Create an AVPlayer using the AVPlayerItem
              player = AVPlayer(playerItem: playerItem)

              // Add the AVPlayerLayer to the mediaView
              let playerLayer = AVPlayerLayer(player: player)
              playerLayer.frame = view.bounds
              playerLayer.videoGravity = .resizeAspectFill
        playerLayer.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions(), context: nil)

            view.layer.addSublayer(playerLayer)

              // Play the content using the AVPlayer
              player?.play()
    }
    private func observePlayerItemProperties(for item: AVPlayerItem) {
        item.observe(\.status, changeHandler: self.onStatusObserverChanged)
        print(item.observe(\.status, changeHandler: self.onStatusObserverChanged))
    }

    private func onStatusObserverChanged(playerItem: AVPlayerItem, change: NSKeyValueObservedChange<AVPlayerItem.Status>) {
        guard playerItem.status != .failed else {
            if let error = playerItem.error as? Error {
                // DRM Errors handled here
                print("DRM errors:\(error)")

            }
            return
        }
    }




    let keyServerUrl: URL = URL(string: "https://mw.hivesys.net/")!

}

extension ViewController: AVAssetResourceLoaderDelegate {
    
    

    func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {

        
        let url =  URL(string: "https://z2cltd.akamaized.net/mini01/vods/49/s1e1-andhera-ujala-ghani-chaon-copy-3395/64366c9ccc181-4606-mp4-cbcs.mpd")
        
        // Get the content id. Content id will be stored in the host of the request url
        guard let contentId = url!.host, let contentIdData = contentId.data(using: String.Encoding.utf8) else {
            print("Unable to read the content id.")
         //   loadingRequest.finishLoading(with: DRMError.noContentIdFound)
            return false
        }
        
        // Request SPC data from OS
        var _spcData: Data?
        var certificateDataNeed: Data?

       // var _spcError: Error?
        let certificateUrl = URL(string: "https://mw.hivesys.net/static/FairPlay.der")!
        let certificateDataTask = URLSession.shared.dataTask(with: certificateUrl) { (data, response, error) in
                  if let certificateData = data {
                   //   let contentIdHeaderValue = "spc=\(contentIdBase64String ?? ""), cert=\(certificateData.base64EncodedString(options: []))"
                      certificateDataNeed = certificateData
                      // Use the obtained certificateData and contentIdHeaderValue as needed
                      // Handle the provided content key request and provide the necessary response
                      // This involves retrieving the license from the license server and providing it to the key request
                      print("Data :\(certificateData)")
                    //  print("contentIdHeaderValue :\(contentIdHeaderValue)")
                      do {
    // **This is where I'm getting stucked unable to get spcData**
                          _spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: [AVAssetResourceLoadingRequestStreamingContentKeyRequestRequiresPersistentKey: true as AnyObject])
                          
                          
                          guard let spcData = _spcData, let dataRequest = loadingRequest.dataRequest else {
                             // loadingRequest.finishLoading(with: DRMError.noSPCFound(underlyingError: _spcError))
                              print("Unable to read the SPC data.")
                              return()
                          }
                          
                          let stringBody: String = "spc=\(spcData.base64EncodedString())&assetId=\(contentId)"
                          var ckcRequest = URLRequest(url: self.keyServerUrl)
                          ckcRequest.httpMethod = "POST"
                          ckcRequest.httpBody = stringBody.data(using: String.Encoding.utf8)
                          URLSession(configuration: URLSessionConfiguration.default).dataTask(with: ckcRequest) { data, _, error in
                              guard let data = data else {
                                  print("Error in response data in CKC request: \(error)")
                                 // loadingRequest.finishLoading(with: DRMError.unableToFetchKey(underlyingError: _spcError))
                                  return
                              }
                              // The CKC is correctly returned and is now send to the `AVPlayer` instance so we
                              // can continue to play the stream.
                              guard let ckcData = Data(base64Encoded: data) else {
                                  print("Can't create base64 encoded data")
                                  //loadingRequest.finishLoading(with: DRMError.cannotEncodeCKCData)
                                  return
                              }
                              // If we need non-persistent token, then complete loading
                              // dataRequest.respond(with: data)
                              // loadingRequest.finishLoading()
                          
                              // If we need persistent token, then it is time to add persistence option
                              var persistentKeyData: Data?
                              do {
                                  persistentKeyData = try loadingRequest.persistentContentKey(fromKeyVendorResponse: ckcData, options: nil)
                              } catch {
                                  print("Failed to get persistent key with error: \(error)")
                                 // loadingRequest.finishLoading(with: DRMError.unableToGeneratePersistentKey))
                                  return
                              }
                              // set type of the key
                              loadingRequest.contentInformationRequest?.contentType = AVStreamingKeyDeliveryPersistentContentKeyType
                              dataRequest.respond(with: persistentKeyData!)
                              loadingRequest.finishLoading()
                          
                          }.resume()

                          
                      } catch let error {
                       //   _spcError = error
                          print("Failed to get stream content key with error: \(error)")
                      }
                    
                  }
              }
              certificateDataTask.resume()
        return true
    }
}

iOS Swift DRM Netflix Fairplay

评论

1赞 Cy-4AH 5/4/2023
AVPlayer 不支持 DASH 和 Widevine。为此,您将需要第 3 方播放器。
1赞 zaid afzal 5/5/2023
你能告诉我哪个第三方玩家会这样做吗?

答:

0赞 Abdelrahman 5/31/2023 #1

AVPlayer 不支持 DASH (.mpd) 和 WideVine。AVPlayer 支持 HLS 流,并且仅支持 Apple FairPlay DRM 系统用于受保护的流。

请参阅 Apple 设备的 Apple 规格文稿。

https://developer.apple.com/documentation/http-live-streaming/hls-authoring-specification-for-apple-devices