1

We are developing a iOS music app. For content protection we are going to use Apples fairplay DRM system. I am following apple's HDLCatalog example for reference. While implementing i noticed there are two methods in AssetLoaderDelegate class that need to be implemented. I will appreciate if any one can help me out how to implemented below two methods. Thanks in advance.

1.)

public func fetchApplicationCertificate() -> Data? {

    // MARK: ADAPT: YOU MUST IMPLEMENT THIS METHOD.
    let applicationCertificate: Data? = nil

    if applicationCertificate == nil {
        fatalError("No certificate being returned by \(#function)!")
    }        


    return applicationCertificate
}

2.)

public func contentKeyFromKeyServerModuleWithSPCData(spcData: Data, assetIDString: String) -> Data? {

    // MARK: ADAPT: YOU MUST IMPLEMENT THIS METHOD.
    let ckcData: Data? = nil

    if ckcData == nil {
        fatalError("No CKC being returned by \(#function)!")
    }


    return ckcData
}

I am updating here that we managed to implement fetchApplicationCertificate() method. Now we are facing problems for generating ckc data

rajnish kumar
  • 11
  • 1
  • 4

2 Answers2

0

Application Certificate

The application certificate is the DER formated public certificate created when you registered for Fairplay with Apple. This should be placed on a web server (AWS S3 is ideal) and retrieved once per application session.

CKC Data

This is specific to whomever you're using for Fairplay License Services. They will have specified an interface for sending the SPC data from your client to their license server. This could be JSON over REST, SOAP, MQ or anything they chose. You will have to ask them for the API spec.

Alastair McCormack
  • 26,573
  • 8
  • 77
  • 100
0

Step 1 :

 let queue                       = DispatchQueue(label: "fairplay.resourcerequests", attributes: [])

    let url                         = URL(string: videoUrl)!   // Streaming the video from this URL

    let videoAsset                  = AVURLAsset(url: url, options: nil)
    videoAsset.resourceLoader.setDelegate(self, queue: queue)

Step 2:

extension PlayerViewController : AVAssetResourceLoaderDelegate{

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

    let url = loadingRequest.request.url
    var error: NSError?
    print("Player Delegate Method")
    print("URL Schema Is  \(url?.scheme! ?? "")")
    if((url?.scheme ?? "") == "skd"),
        let assetString = url!.host, let assetID = assetString.data(using: String.Encoding.utf8) // Get the URI for the content key.{guard let fetchedCertificate = self.fetchedCertificate else { return false} // Get the application certificate from the server.

            let requestedBytes = try loadingRequest.streamingContentKeyRequestData(forApp: fetchedCertificate, contentIdentifier: assetID, options: nil)


            do{

                print("Online Video Streaming Going On")

                let responseData = try contentKeyFromKeyServerModuleWithRequestData(requestedBytes, assetString: assetString, expiryDuration: expiryDuration)
                guard let dataRequest = loadingRequest.dataRequest else {
                    //                        print("Failed to get instance of AVAssetResourceLoadingDataRequest (loadingRequest.dataRequest).")
                    return false
                }

                dataRequest.respond(with: responseData)

                if let infoRequest = loadingRequest.contentInformationRequest,
                    expiryDuration != 0.0
                {

                    infoRequest.renewalDate = Date(timeIntervalSinceNow: expiryDuration)

                    infoRequest.contentType = "application/octet-stream"
                    infoRequest.contentLength = Int64(responseData.count)
                    infoRequest.isByteRangeAccessSupported = false
                }

                // Treat the processing of the requested resource as complete.
                loadingRequest.finishLoading()

                // The resource request has been handled regardless of whether the server returned an error.
                return true

            }catch let e as NSError
            {
                error = e
                //                    print("content key  error\(error)")
            }
        }catch let e as NSError {
            error = e
            // Resource loading failed with an error.
            //                print("streamingContentKeyRequestDataForApp failure: \(error.localizedDescription)")

        }}}

Step 3:

func contentKeyFromKeyServerModuleWithRequestData(_ requestBytes: Data, assetString: String, expiryDuration: TimeInterval?=0.0, persitent:Bool?=true) throws -> Data {
    // If the key server provided a CKC, return it.
    //        if let ckcData = ckcData {
    //            return ckcData
    //        }
    //        else
    //        {
    let base64Decoded = requestBytes.base64EncodedString(options: NSData.Base64EncodingOptions())

    //MARK: Get Fairplay license for the current user
    NSLog("using ticket: %@", streamTicket )
    if let returnData:Data = mediaMakerDRMLicenseCall(base64Decoded, ticket: streamTicket)
    {
        if returnData.count <= 0
        {

            //                 Otherwise, the CKC was not provided by key server. Fail with bogus error.
            //                 Generate an error describing the failure.

            throw NSError(domain: "com.example.apple-samplecode", code: 0, userInfo: [
                NSLocalizedDescriptionKey: NSLocalizedString("Item cannot be played.", comment: "Item cannot be played."),
                NSLocalizedFailureReasonErrorKey: NSLocalizedString("Could not get the content key from the Key Server.", comment: "Failure to successfully send the SPC to the Key Server and get the content key from the returned Content Key Context (CKC) message.")
                ])
        }
        else
        {
            return returnData
        }
    }
    //}
}

Step 4:

func mediaMakerDRMLicenseCall(_ playerSPC : String, ticket : String) -> Data{// API Call to fetch license from client server}
Mayur Rathod
  • 361
  • 3
  • 13