I am trying to write a custom resourceLoader delegate to work with an AVURLAsset. I have started with the AVARLDelegateDemo code from Apple. I am trying to playback an HLS url. I am testing on an iPad.
What I notice is that the playlist file (.m3u8) gets downloaded correctly. Then video file (.ts) also gets downloaded. I know that the .ts file is downloaded because I can see the GET request completing on the web server with status 200. I also set a breakpoint at the following line:
[loadingRequest.dataRequest respondWithData:data];
The length of data matches the file size and the first byte is the sync byte of the .ts (0x47) as expected.
The problem is that the app displays an error code. The following dialog pops up:
"The operation could not be completed. An unknown error occurred (-12881)"
Googling for this error has not turned up any information. I do not know what to check for or how to get more information. It is not as if the app is crashing and giving me a stack trace. The video refuses to play and I get no more information beyond the "unknown error -12881" This is not a lot to go on.
Also, if I point an unmodified version of the demo code at my playlist, the video plays just fine.
Can anyone tell me what is going wrong? Here is the code from my customized resource loader.
- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
NSURLRequest *redirect = nil;
redirect = [self generateRedirectURL:(NSURLRequest *)[loadingRequest request]];
if (redirect)
{
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:redirect.URL
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
[loadingRequest.dataRequest respondWithData:data];
[loadingRequest finishLoading];
}] resume];
} else
{
[self reportError:loadingRequest withErrorCode:badRequestErrorCode];
}
return YES;
}
For the record:
I contacted Apple Developer Tech Support about the issue with trying to use the AVAssetResourceLoaderDelegate to get access to the .ts files. This approach does not work because:
"
It is not possible to have access to the data as it is being
downloaded. iOS only allows only the following to be returned via
AVAssetResourceLoaderDelegate for HTTP Live Streaming media:
- key requests
- playlist
- media redirects
"
For my use case, I ended up using a local web server (https://github.com/swisspol/GCDWebServer) and sending all requests to a web server within the app. This web server then makes requests to the remote server
Related
I have an app that streams audio protected by FairPlay DRM. It originally shipped using AVAssetResourceLoaderDelegate to deliver FairPlay keys, but now I'm updating it to use AVContentKeySession for iOS 11.2 and later. Side note: if you're trying to do this and are frustrated at the lack of documentation, there's sample code at the "FairPlay Streaming Server SDK (4.2.0)" link here.
Each of my audio products is broken into many tracks. When I open an audio product, I queue up more than one track, via AVQueuePlayer. Each of those tracks generates a request for a FairPlay key. In the case when there is no persistent key already downloaded, each of those requests goes to the key server, downloads a key, generates a persistent key, and saves it. Each track has the same key, so they all end up with the same persistent key data, and each one overwrites the last one to finish.
Because the cost of my key servers is dependent on the number of key requests I make, I'd like to have only the first request actually hit the key server, and subsequent requests use the persistent key. But the method used to get the SPC data to pass up to the key server, makeStreamingContentKeyRequestDataForApp, uses an async completion block. The equivalent method on AVAssetResourceLoadingRequest is synchronous.
My question: is it safe to force this call to be synchronous using a semaphore? Like this:
-(void)handleOnlineRequest:(AVContentKeyRequest *)req
NSData *appCert = [self _getAppCertData];
NSData *assetId = [self _kidFromRequest:req];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[req makeStreamingContentKeyRequestDataForApp:appCert
contentIdentifier:assetId
options:nil
completion:^
NSData *contentKeyRequestData, NSError *error)
{
//request key data and respond to request
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
The effect is especially pronounced when downloading audio that has not been streamed before. Download speeds for audio using AVAssetDownloadTask are very slow, so I initiate many at once, and each one generates a key request.
I'm using the dropbox SDK to save file to a user's dropbox account.
When the user taps 'save to dropbox' button for the first time, a popup window pops up and the user is required to login onto their dropbox account. I then upload a file to their dropbox account using uploadFile method provided by the SDK. However, the first time, it gives me the error:
DropboxSDK: error making request to /1/files_put/dropbox/sampleFile.pdf - (401) Authentication failed
When I close the app and try again, it successfully uploads the file.
What may be causing the app to behave so strangely?
I had the same issue and it turned out that I initialized my DBRestClient from viewDidLoad like the Dropbox docs say, but since that happens before the dropbox account is linked the restClient is not properly set.
This can be easily fixed re-initializing your restClient or even better by using the following way to access your restClient.
-(DBRestClient*)restClient{
if(_restClient == nil){
_restClient = [[DBRestClient alloc] initWithSession:[DBSession sharedSession]];
[_restClient setDelegate:self];
}
return _restClient;
}
I got same error, use this code in your app hope this will help you.
if([[DBSession sharedSession] isLinked])
{
//Do your drop box work here...
}
else
{
//If not linked then start linking here..
[[DBSession sharedSession] linkFromController:self];
}
Description of error is below:
Error Domain=com.apple.LocalAuthentication Code=-1000 "Pending UI mechanism already set." UserInfo=0x17406b0c0 {NSLocalizedDescription=Pending UI mechanism already set.}
I am also trying Apple's Sample Example app and getting same error. Previously it was working fine, but it has stopped working suddenly ad not working. Please help.
I am using iPhone 6 with iOS 8.1
This code just worked fine for me.
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = #"String explaining why app needs authentication";
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
// User authenticated successfully, take appropriate action
NSLog(#"User authenticated successfully, take appropriate action");
} else {
// User did not authenticate successfully, look at error and take appropriate action
NSLog(#"User did not authenticate successfully, look at error and take appropriate action");
}
}];
} else {
// Could not evaluate policy; look at authError and present an appropriate message to user
NSLog(#"Could not evaluate policy: %#",authError);
}
Don't forget to import Local Authentication framework <LocalAuthentication/LAContext.h>. Hope this will solve your issue.
Try rebooting your phone.
I also started getting this error and decided to see if other apps were affected. I have both Dropbox and Mint set up for Touch ID. Sure enough Touch ID wasn't working for them either and they were falling back to passcodes.
I rebooted my phone and it started working again, so it would seem the Touch ID can bug out and stop working. I'm on iOS 8.2 btw.
I guess the proper way to handle this condition is like those apps do and fallback to password / passcode.
I need to upload files in the background with other HTTP POST requests before and after each upload.
Previously I was using beginBackgroundTaskWithExpirationHandler which was working perfectly till iOS 6 but from IOS 7 it is restricted for approx 180 seconds only which is a concern.
I have read the documents regarding NSURLSession were in we have Background transfer service. But the problem with this is it only allows upload and download in background. It doesn't allow me to make POST request after every upload in the background.
So is there any way to make the POST request along with the background uploads?
Any hint in the right direction would be highly appreciated.
I think you can use NSURLSessionDownloadTask to send a POST.
IMO, download task doesn't mean it is used for download. it means the response of your POST request (json/xml) will be downloaded to a local file. Then you can open that file and parse it to get the request.
if you want you can even use a NSURLSessionDownloadTask to upload files to S3. and the s3 response will be 'downloaded' to a local file..
for more information, see this question in the apple developer forum https://devforums.apple.com/thread/210515?tstart=0
I successfully round-trip a vanilla http call during a background task's callback, in production code. The callback is:
-[id<NSURLSessionTaskDelegate> URLSession:task:didCompleteWithError:]
You get about 30 seconds there to do what you need to do. Any async calls made during that time must be bracketed by
-[UIApplication beginBackgroundTaskWithName:expirationHandler:]
and the "end task" version of that. Otherwise, iOS will kill your process when you pop the stack (while you are waiting for your async process).
BTW, don't confuse UIApplication tasks (I call them "app tasks") and NSURLSession tasks ("session tasks").
If you use uploadTaskWithRequest:fromData:completionHandler: you can make your HTTP POST request from the completion handler block:
[backgroundSession uploadTaskWithRequest:request fromData:data completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestForURL:[NSURL URLWithString:#"http://somethingorother.com/"]];
request.HTTPMethod = #"POST";
.
.
.
NSURLResponse *postResponse;
NSError *postError;
NSData *postResponseData = [NSURLConnection sendSynchronousRequest:postRequest returningResponse:&postResponse error:&postError];
// Check postResponse and postError to ensure that the POST succeeded
}
}];
What does a web service need to do to cause NSURLConnection's delegate to receive the connection:didFailWithError: message?
For example:
iOS app passes a token to the web service, web service looks up the token, and then the web service needs to respond with an error saying "invalid token" or something of the like.
Currently, the data is received, and in "connectionDidFinishLoading:" is parsed for error messages. This means I'm error checking in two places, which I am trying to avoid.
I have both the iOS app and web service completely under my control.
In my experience (the three most dangerous words in programming), -connection:didFailWithError: is only called if the HTTP exchange failed. This is usually a network error or maybe an authentication error (I don't use authentication). If the HTTP message succeeds, no matter the response code, -connectionDidFinishLoading: is called.
My solution: call -connection:didFailWithError: when I detected an error. That way all my error handling code is in one place.
At the top of my -connectionDidFinishLoading:, I have:
NSError *error;
NSDictionary *result = [self parseResultWithData:self.connectionData error:&error];
if (!result) {
[self connection:connection didFailWithError:error];
return;
}
There are many conditions on which the delegate connection:didFailWithError: of NSUrlConnection may invoke.Here's a list of those errors or constants.I think an alertview would be better to show http errors in connection:didFailWithError:.
-(void) connection:(NSURLConnection *)connection didFailWithError: (NSError *)error
{
UIAlertView *errorAlert= [[UIAlertView alloc] initWithTitle: [error localizedDescription] message: [error localizedFailureReason] delegate:nil cancelButtonTitle:#"Done" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
NSLog (#"Connection Failed");
}
While not directly related to your question, I would encourage you to move to a more high level library. I can heartily recommend AFNetworking, it is production ready and I have used it in many projects. This will allow you to inspect the response code of each request in the failure block. This project also abstracts away a lot of the low level handling that you would otherwise be required to write for network communication; I'm speaking here about parsing and creating XML / JSON strings to communicate with a service.
To give you a more focused answer to your question, I would call the cancel method of your NSURLConnection once you have noticed an error in connectionDidFinishLoading:. This will automatically cancel the request and call the failure method of the delegate object.
The documentation for NSURLConnection is pretty dry, and the failure method of the delegate does not specifically document the failure cases. You may be able to find more information in the URL Loading System Programming Guide.
I couldn't see the forrest for the trees.
I needed to step back from connection:didFailWithError: and look at a different delegate method connection:didReceiveResponse:!!
With the web service fully under my control, the endpoint could respond with a 500 status code, which gets picked up in connection:didReceiveResponse, and pass along some JSON further explaining the situation, which gets picked up and processed in connection:didReceiveData:.
The NSURLConnection delegate hangs onto a couple more bits of state throughout the process, but it has the best code smell I've found so far.
Jeffery's answer was by far most correct: the connection:didFailWithError: callback is only in relation to the network failing, any response from the web service means the connection didn't fail!