Playing FairPlay streams with TVJS

Has anyone managed to play FairPlay streams using TVJS? According the MediaItem documentation three functions should be set as properties on the MediaItem for FairPlay streaming to work:

  • loadCertificate
  • loadAssetID
  • loadKey


In our case all three functions are set but loadKey is never called. Our first suspicion was that we are not calling the callback in loadCertificate correctly - we have tried both calling it with a base64-encoded certificate (that's how we receive it from our server) and a decoded version but the result is the same in both cases - loadAssetID is called but never loadKey and playback never starts.


Any more documentation or examples on how to play FairPlay streams using the TVJS Player would be greatly appreciated!


Edit: this is the error we get:


#T:[Main] #Error #SYSTEM : Error occurred: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" 
UserInfo={NSUnderlyingError=0x12fe76330 {Error Domain=NSOSStatusErrorDomain Code=-345003 "(null)"}, 
NSLocalizedFailureReason=An unknown error occurred (-345003), NSLocalizedDescription=The operation could not be completed}

Accepted Reply

Had the same issue. Base64 encoding both the certificate and the AssetID solved the problem.

Replies

If anyone has been able to play FairPlay streams using the TVJS Player at all that would be of interest to know as well. As it is now we're not sure if we should report it as a bug or if we are doing something wrong.

Nope. Got same problem.

Had the same issue. Base64 encoding both the certificate and the AssetID solved the problem.

Did you finally get this working? None of the three callbacks are being fired. I attach the functions on each mediaitem as I queue them up. Any updates/insights would be much appreciated.

how did you setup the media item that it called the callbacks for loadAssetID, etc? None of my functions are firing off on playback. All I get is an error in the console


2016-01-24 05:15:52.876 OneMSBase[424:65771] #T:[Main] #Error #SYSTEM : Error occurred: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x128509b40 {Error Domain=NSPOSIXErrorDomain Code=103 "Policy not found"}, NSLocalizedFailureReason=An unknown error occurred (103), NSLocalizedDescription=The operation could not be completed}

Yes, we got it working - our issue was also that the asset id wasn't base64 encoded.


We attach the functions to the media items like this:


mediaItem.loadAssetID = function(url, callback){ ... }
mediaItem.loadCertificate = function(url, callback){ ... }
mediaItem.loadKey = function(url, requestData, callback){ ... }


I suspect that you are doing it the same way? We only have one media item in the playlist, not sure if that matters.

Could there be an issue with the streams themselves so that they are not recognized as being fairplay streams, have you testet them in another app?

Add a Comment

Thank you! This was precisely our issue as well.

That's how I did it initially. I then changed it to be like so:


mediaItem.loadAssetID = getFPSAssetId;
mediaItem.loadCertficate = getCertificate;
mediaItem.loadKey = getFPSKey;

where getFPSAssetId, getCertificate, getFPSKey are defined at the top of the JS page like so:

function getFPSAssetId(uri, callback) {
  console.log("===== HERE === ");
    var assetID = fpsMgr.assetIDFromURI(uri);


    if (assetID != null) {
        console.log("======= [FPS] =============== [FPS]: Successfully parsed asset ID: " + assetID);
        callback(assetID);
    } else {
        console.log("=============== [FPS]: Error parsing asset ID from URI: " + uri);
        callback(null, "Error parsing asset ID from URI: " + uri);
    }
};


function getCertificate(uri, callback) {
    console.log("==== Getting the certificate");

    var certUrl = fpsMgr.getFPSCertURL();
    SkyChnlApi.debugLog("======= [FPS] certUrl: " + certUrl);
    http.apiRequest(certUrl, "GET", true, "", function(certRequest) {
        if (certRequest.status == 200) {
            console.log("Received cert data: " + certRequest.responseDataAsBase64);
            callback(certRequest.responseDataAsBase64);
        } else {
            console.log("Error receiving cert data" + certRequest.status);
            callback(null, "Got bad response from server: " + certRequest.status);
        }
    });
};


function getFPSKey(uri, requestData, callback) {
    console.log("======= [FPS] getting the FPS asset key");


    var assetID = fpsMgr.assetIDFromURI(uri);
    var postBody = "payload=" + requestData + "&id=" + assetID;
    // if (fpsToken) postBody += "&fpstoken=" + fpsToken;
    // fpsToken = null; //reset right away.


    var keyUrl = fpsMgr.getFPSCKCURL();
    console.log("======= [FPS] keyurl: " + keyUrl + " sending " + postBody);


    httpMgr.apiRequest(keyUrl, "POST", true, postBody, function(keyRequest) {
        if (keyRequest.status == 200) {
            // The example server implementation wraps the key response blob in
            // <ckc>...</ckc> tags.  Partners can encode the key response in any
            // manner they wish.
            console.log("======== " + keyRequest);
            var response = keyRequest.responseText;
            console.log("=============== [FPS]: " + response);
            callback(response, null, null);


        } else {
            console.log("=============== [FPS]: Got bad response from server: " + keyRequest.status);
            callback(null, null, "Got bad response from server: " + keyRequest.status);
        }
    });
    // callback(keyValue, renewDate, error);
};


fpsMgr and httpMgr represent objects that are supposed to return relevant information. My whole confusion is that even as defined I don't see any log statements. I would expect that even if I didn't base64 encode the information I would at least see the console statements at the beginning of each function right?

Can anyone provide any examples?

Im stuck at the certificate stage as the provider returns the certifcate as Content-Type: application/octet-stream

and I have no idea how to handle this such that I can convert to base64 and continue the process.

I have specified the requestType as "blob", but how then do I convert the response?

I have the same probleme over here, I can't see my logs and attributes "loadAssetID", "loadCertficate", "loadKey" doesn't seem to be called. In xcode console, I can see the following error (event if I am sure that the media I am trying to play is a valid Fairplay media, since I use it on a ios mobile app) :


Error occurred: Error Domain=AVFoundationErrorDomain Code=-11833 "See -[AVPlayerItem errorLog] for 1 events" UserInfo={NSLocalizedDescription=Cannot Decode, NSLocalizedFailureReason=The decoder required for this media cannot be found., AVErrorMediaTypeKey=vide, NSDebugDescription=See -[AVPlayerItem errorLog] for 1 events, NSUnderlyingError=0x7f8cbdb95370 {Error Domain=NSOSStatusErrorDomain Code=-12906 "(null)"}}

My problem was that I was running my AppleTV App in the emulator. When running on the AppleTV, I see my traces and the attributes "loadAssetID", "loadCertficate", "loadKey" are called

I do have problems and I am stuck at the stage of retreiving the certifcate. I did a new post here since my problem seem to be linked with the XMLHttpRequest object in TVJS: https://forums.developer.apple.com/message/138947#138947

I did solve my problem :


1- Make sur you run your code on the AppleTv and not the Simulator

2- In TVJS (javascript), when you use the XMLHttpRequest object to request the certificate, make sure YOUR SERVER returns a base64 string version of the certificate (and not a byteArray version). It's seems that the "XMLHttpRequest" object in TVJS is bugged when you passe the "responseType = 'arraybuffer'".

Would you mind sharing your code on this or even the m3u8 URL you are using so I can compare mine with what you are sending? I still can't get any of the functions to be called. this is what I get before the player stops


Error occurred: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x1371d52b0 {Error Domain=NSPOSIXErrorDomain Code=103 "Policy not found"}, NSLocalizedFailureReason=An unknown error occurred (103), NSLocalizedDescription=The operation could not be completed}

can someone please post working code for fairplay implementation?