I'm fairly new to webrtc, and quite experienced in iOS.
We have a media server setup on AWS (all ports open), with kurento one2one client. Everything is working well when I call from desktop to desktop. when I call from desktop to iOS it works (i.e. incoming calls to iOS).
Issue is when I make an outgoing call from iOS to desktop, it doesn't work.
Below are the server logs, so I think call is getting connected...
Received message: {"id":"incomingCall","from":"qqq"}
spec: {"audio":true,"video":{"width":640,"framerate":15}}
chrome: {"audio":true,"video":{"optional":[{"minWidth":640},{"maxWidth":640},{"minFramerate":15},{"maxFramerate":15}]}}
Sending message:{"id":"incomingCallResponse","from":"qqq","callResponse":"accept","sdpOffer”:”huge text, so removed”}
Received message: {"id":"startCommunication","sdpAnswer":"huge text, so removed”}
When call is clicked I'm doing below on iOS:
[peerConnection offerForConstraints:[self defaultOfferConstraints] completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {
[peerConnection setLocalDescription:sdp completionHandler:^(NSError * _Nullable error) {
NSLog(#"%#",error.description);
}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
NSDictionary *registerMessage = #{
#"id": #"call",
#"from": #"qqq",
#"to": #"www",
#"sdpOffer" : sdp.description,
};
NSData *messageData = [NSJSONSerialization dataWithJSONObject:registerMessage
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *messageString =
[[NSString alloc] initWithData:messageData encoding:NSUTF8StringEncoding];
[webSocket send:messageString];
}];
}];
and after I get accept response I do below,
if ([answerID isEqualToString:#"callResponse"]) {
NSString *answerRespose = [wssMessage objectForKey:#"response"];
if ([answerRespose isEqualToString:#"accepted"]) {
description = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdpAnswer];
[peerConnection setRemoteDescription:description completionHandler:^(NSError * _Nullable error) {
}];
}
}
I'm also taking care of ICECandidates...
As incoming works without any issue, I'm assuming that no mistake was made in creating stream.
Am I missing something for outgoing call?
I finally found it out.
Issue was with ICECandidates, I came to know that adding ICECandidate after setting remote description works.
Made an array ICECandidates collected from web socket and added them once I got remote RemoteDescription :)
I hope this helps someone in future.
Related
I'm trying out Snpachat's SnapKit login api, and I've setup my project as described in the documentation/guide. I've allowed the use of all the scopes, i.e. external id, display name and bitmoji in the dashboard and added the required fields in the .plist of my app.
The login and authentication proceed normal and return successfully, but when I try to fetch user data, that request fails every time with the SCOAuth2ClientErrorDomain error.
I'm using the snippet provided within the guide (although that code has a typo and doesn't build as is so I doubt in the validity of that code):
[SCSDKLoginClient loginFromViewController:self completion:^(BOOL success, NSError * _Nullable error) {
NSString *graphQLQuery = [#"{me{displayName, bitmoji{avatar}, externalId}}" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSDictionary *variables = #{#"page": #"bitmoji"};
[SCSDKLoginClient fetchUserDataWithQuery:graphQLQuery
variables:variables
success:^(NSDictionary *resources) {
NSDictionary *data = resources[#"data"];
NSDictionary *me = data[#"me"];
NSString *displayName = me[#"displayName"];
NSDictionary *bitmoji = me[#"bitmoji"];
NSString *bitmojiAvatarUrl = bitmoji[#"avatar"];
} failure:^(NSError * error, BOOL isUserLoggedOut) {
// handle error as appropriate
}];
}];
I've even tried configurin my app without the bitmoji and tried the request without it, it still fails.
[SCSDKLoginClient loginFromViewController:self completion:^(BOOL success, NSError * _Nullable error) {
NSString *graphQLQuery = [#"{me{displayName, externalId}}" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[SCSDKLoginClient fetchUserDataWithQuery:graphQLQuery
variables:nil
success:^(NSDictionary *resources) {
NSDictionary *data = resources[#"data"];
NSDictionary *me = data[#"me"];
NSString *displayName = me[#"displayName"];
} failure:^(NSError * error, BOOL isUserLoggedOut) {
// handle error as appropriate
}];
}];
Anyone have any idea what I might be doing wrong?
Ok, I got it working.
First of all, I re-added the user I was testing with to the demo users in the developer portal. After that, the SCOAuth2ClientErrorDomain error was gone and I was getting the success callback.
But, the response data was an error in the query string. The problem is that they're using a deprecated method stringByAddingPercentEscapesUsingEncoding. I'm not sure what the exact problem is but I've just sent the raw string as a query and I got a valid response.
UPDATE: I think encoding here is not needed. It doesn't make sense for the user of the api to encode the query. The api should handle it internally, and I think that is what might be happening here. So you probably get a double encoded query which then isn't encoded properly and is invalid. I tested encoding with non-deprecated methods for URL queries and it still didn't work. A raw query string is the way to go.
I'm trying to use sync through an IIS 8 Webdav backend with Ensembles. The problem i encounter is that the first sync works fine, but when i try to sync a second time or on a second unit (iPad in this case) I get a server error 405 "method not allowed". Has anyone encountered this and got it working, to sync with IIS Webdav?
This is the allheaderfield property of the server response:
" UserInfo={NSLocalizedDescription=HTTP status code was {
Allow = "COPY, PROPFIND, DELETE, MOVE, PROPPATCH, LOCK, UNLOCK";
Connection = "Keep-Alive";
"Content-Length" = 1293;
"Content-Type" = "text/html";
Date = "Mon, 25 Jan 2016 12:02:07 GMT";
"Persistent-Auth" = true;
Server = "Microsoft-IIS/8.5";
"X-UA-Compatible" = "IE=8";
EDIT:
It might be possible that this isn't a configuration problem after all. I added a few logs and the createDirectoryAtPath method gives me HTTP error 405, this is the original code:
- (void)createDirectoryAtPath:(NSString *)path completion:(CDECompletionBlock)completion{
NSMutableURLRequest *request = [self mutableURLRequestForPath:path];
request.HTTPMethod = #"MKCOL";
[request setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
[self sendURLRequest:request completion:^(NSError *error, NSInteger statusCode, NSData *responseData) {
if (completion) completion(error);
}];}
And this is the directoryExistsAtPath method:
- (void)directoryExistsAtPath:(NSString *)path completion:(CDEDirectoryExistenceCallback)completion{
[self sendPropertyFindRequestForPath:path depth:0 completion:^(NSError *error, NSInteger statusCode, NSData *responseData) {
if (error && statusCode != 404) {
if (completion) completion(NO, error);
}
else if (statusCode == 404) {
if (completion) completion(NO, nil);
}
else {
CDEWebDavResponseParser *parser = [[CDEWebDavResponseParser alloc] initWithData:responseData];
BOOL succeeded = [parser parse:&error];
if (!succeeded) {
if (completion) completion(NO, error);
return;
}
BOOL isDir = [parser.cloudItems.lastObject isKindOfClass:[CDECloudDirectory class]];
if (completion) completion(isDir, nil);
}
}];}
If i replace the first parameter (currently the isDir-variable) in the completion block at the end to YES, the 405 error does not appear.On logging the parser.clouditems.lastobject, I find that it is often (or always?) empty). So setting the parameter to YES, results in data being uploaded to my webdav, and the folders are in place. However, testing on a second unit (or reinstalling the app on the same unit), download never happens - the downloadFromPath never gets called, a "GET"-request is never sent.
Looking at the calling code in the underlying framework (CDECloudmanager mostly) hasn't led me anywhere so far.
As the directoryExistsAtPath is optional, i tried commenting it out, but i don't think it made a difference.
Another thing I noticed is that I get several baseline files in the baselines folder. According to the Ensembles documentation, there should only be one.
Any clues?
Ok seems I got this working at last. I had to make a change in the CDEWebDavCloudFileSystem class to avoid the 405 error. Doing that however, I encountered a 404 error. That one was solved by configuring the IIS webdav.
So step 1, I changed the request in the sendPropertyFindRequestForPath method:
new code:
static NSString *xml = #"<?xml version=\"1.0\" encoding=\"utf-8\" ?><D:propfind xmlns:D=\"DAV:\"><D:allprop/></D:propfind>";
original code:
static NSString *xml = #"<?xml version=\"1.0\" encoding=\"utf-8\" ?><D:propfind xmlns:D=\"DAV:\"><D:prop><D:href/><D:resourcetype/><D:creationdate/><D:getlastmodified/><D:getcontentlength/><D:response/></D:prop></D:propfind>";
To remove the 404 error appearing after that I had to add mime type application/xml to the .cdeevent extension.
This link goes into detail of how to configure IIS for that:
http://anandthearchitect.com/2013/08/01/webdav-404file-or-directory-not-found/
I'm trying to create an iOS app that uses OAuth2 authentication using the native iOS NSURLSession URL loading classes. I gain an access token fine using the directions here:
http://www.freesound.org/docs/api/authentication.html
I subsequently launch the application and run a search query
https://www.freesound.org/apiv2/search/text/?query=snare
The request header fields looks like this (note my access token is not expired and I have confirmed it is the same as I received from performing the steps above)
{
"Authorization: Bearer" = MY_ACCESS_TOKEN;
}
This fails with:
{"detail": "Authentication credentials were not provided."}
The response headers look like this:
{
Allow = "GET, HEAD, OPTIONS";
Connection = "keep-alive";
"Content-Type" = "application/json";
Date = "Sat, 31 Jan 2015 13:56:32 GMT";
Server = "nginx/1.2.1";
"Transfer-Encoding" = Identity;
Vary = "Accept, Cookie";
"Www-Authenticate" = "Bearer realm=\"api\"";
}
The funny thing is that this does not always happen. If I repeat this entire process a number of times, deleting the app in between, it will eventually work. Once it works, it will continue to work while I'm developing. Sometimes then when I come back to it, say the next day, it stops working and I need to repeat this deleting and re-installing routine to get it back working again!
There's an authentication challenge delegate method on NSURLSession that will get called if implemented. It's a 'server trust' challenge. Could this be something to do with it? Would you even expect an authentication challenge of this nature? There's nothing mentioned about it in the docs alluded to above.
Any help would be much appreciated.
EDIT
This is how the search text ("snare") GET call is made.
I basically pass in an NSMutableURLRequest with the URL set to the above (https://www.freesound.org/apiv2/search/text/?query=snare). useAccessToken is set to YES.
- (void)makeRequest:(NSMutableURLRequest *)request useAccessToken:(BOOL)useAccessToken completion:(CompletionBlock)completion {
NSAssert(completion, #"No completion block.");
if (useAccessToken) {
NSString *accessToken = [[ODMFreesoundTokenCache sharedCache] accessToken];
NSAssert(accessToken.length, #"No access token.");
[request addValue:accessToken forHTTPHeaderField:#"Authorization: Bearer"];
}
NSLog(#"Making request: %# \n\nWith access token: %#", request, [[ODMFreesoundTokenCache sharedCache] accessToken]);
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSInteger code = [(NSHTTPURLResponse *)response statusCode];
if (code == 200) {
if (!error) {
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"json: %#", json);
completion(json, error);
}
else {
completion(nil, error);
}
}
else {
NSString *reason = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *error = [NSError errorWithDomain:#"Request Error" code:code userInfo: reason ? #{NSLocalizedDescriptionKey : reason} : nil];
NSLog(#"error: %#", error);
completion(nil, error);
}
}];
[task resume];
}
The 2 flows for authentication described in the doc are not "safe" for a device. Using API keys would require the secret to be stored in the device.
The OAuth2 flow they support (authorization_code) requires a server to server call to exchange a code for the actual token (This step: http://www.freesound.org/docs/api/authentication.html#step-3). This call requires another credential (the client_secret that you probably should not store in the device either.
You need a server in between that negotiates this for you. Or a server that translates the code flow into token one. (Illustrated here: https://auth0.com/docs/protocols#5).
We have more number of devices(200+) connected to network to communicate with server, often the connection fails and does not reconnect as mentioned in the question I am getting the mentioned errors.
The code which we use to send request is
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:#"http://ipaddress"];
NSMutableURLRequest *menuRequest=[[NSMutableURLRequest alloc]initWithURL:url];
NSURLResponse *response;
NSError *error=nil;
NSData *data = [NSURLConnection sendSynchronousRequest:menuRequest returningResponse:&response error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
if(!error)
{
NSLog(#"Request Success");
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData: data];
[xmlParser setDelegate:self];
[xmlParser parse];
}
else
{
NSLog(#"Failed with error-----%#",error);
}
});
});
This request is sent every minute.
It happens only in client environment we need to recover from this connection failure and reconnect(the next request does not get success even if the server is up).This happens in both iOS 7 & 8.I went through some similar post but I am not able to get the exact reason and also the solution for these problems.Help me out guys.
"I went through some similar post but i am not able to get the exact reason and alos the solution for these problems.Help me out guys."
The errors you are getting are the reasons. Specifically, codes -1001, -1005, and -1004 in the NSURLErrorDomain are time outs, network connection lost, and cannot connect to host. You can look these error codes up in the NSURLErrors.h header. These are all problems with your network connectivity.
The code you have posted has no logic to reconnect, which is why it does not attempt to do so.
Like #quellish said, these errors tell you what happened. I googled a thousand times to investigate codes like -1004. But it is simpler to find them locally...
XCode / Window / Developer Documentation / URL Loading System Error Codes
i want to get all articles from the shopware api(http://wiki.shopware.de/Shopware-API_cat_919.html)
but the i dont get the data into an NSDictionary
url i call: http://myshop.com/api/articles
here is the source i got
NSURL *url = [NSURL URLWithString:weburl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError) {
if (data.length > 0 && connectionError == nil) {
NSDictionary *rest_data = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
_newsDataForTable = [NSMutableArray array];
NSDictionary *news;
for (id key in rest_data[#"postalcodes"]) {
news = [rest_data[#"postalcodes"] objectForKey:key];
}
int iterator = 0;
for (id key in news) {
[_newsDataForTable insertObject:key[#"title"] atIndex:iterator];
iterator++;
}
[_newsTable reloadData];
[_newsTable numberOfRowsInSection:[_newsDataForTable count]];
[_newsTable reloadRowsAtIndexPaths:0 withRowAnimation:UITableViewRowAnimationLeft];
}
}];
}
There are a couple of things in your approach that could use improvement.
First, this is performing networking on the main queue. That is a no-no, wether the networking is synchronous or not. Creating a new NSOperationQueue for your connections and passing that instead of [NSOperationQueue mainQueue] is a huge improvement.
Second, the error handling is incorrect. In general the correct error handling pattern for Objective-C is to check wether a call resulted in the expected result before using the error. In this case, it's the NSURLResponse that should be checked, not the data. NSURLConnection may be able to connect to the remove service just fine but get no data back - and for many HTTP requests this is expected, correct behavior. If there is a problem connecting, the NSURLResponse will be nil. Check wether the response is nil, if it is then handle the error.
You're also not checking the HTTP response status code or MIME type. The server could respond with a 500, indicating a server error, or could mistakenly send you HTML (which would give the JSON parser fits).
A verbose example that does the above correctly is here. :
[NSURLConnection sendAsynchronousRequest:request queue:[self connectionQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (response != nil){
if ([[self acceptableStatusCodes] containsIndex:[(NSHTTPURLResponse *)response statusCode] ]){
// The server responded with an HTTP status code that indicates success
if ([[self acceptableMIMETypes] containsObject:[[response MIMEType] lowerCaseString] ]){
// The server responded with a MIME type we can understand.
if ([data length] > 0){
NSError *jsonError = nil;
id jsonObject = nil;
// The server provided data in the response, which means we can attempt to parse it
// Note that we are not specifying NSJSONReadingMutableContainers or NSJSONReadingMutableLeaves, as this would result in
// an object that is not safe to use across threads.
jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
if (jsonObject != nil){
// The JSON parser successfully parsed the data, and returned an object. There is nothing to tell us what kind of object was returned.
// We need to make sure it responds to the selectors we will be using - ideally, we'd pass this object to a method that takes an
// id parameter, not NSDictionary, and inside that method it would check wether the id object responds to the specific selectors
// it is going to use on it.
if ([jsonObject respondsToSelector:#selector(dictionaryWithDictionary:)]){
[self doStuffWithDictionary:jsonObject];
}
} else {
// The JSON parser was unable to understand the data we provided, and the error should indicate why.
[self presentError:jsonError];
}
} else {
// The server responded with data that was zero length. How you deal with this is up to your application's needs.
// You may create your own instance of NSError that describes the problem and pass it to your error handling, etc.
}
} else {
// The server response was a MIME type we could not understand. How you handle this is up to you.
}
} else {
// The server response indicates something went wrong: a 401 Not Found, etc.
// It's up to your application to decide what to do about HTTP statuses that indicate failure.
// You may create your own instance of NSError that describes the problem and pass it to your error handling, etc.
}
} else {
// Only inspect the error parameter if the response is nil.
// The error indicates why the URL loading system could not connect to the server.
// It is only valid to use this error if the server could not connect - which is indicated by a nil response
[self presentError:connectionError];
}
}];
// Returns the HTTP status codes we find acceptable.
- (NSIndexSet *) acceptableStatusCodes {
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 99)];
}
// Returns the mime types we can accept and understand.
- (NSSet *) acceptableMimeTypes {
NSSet *result = nil;
result = [NSSet setWithObjects:#"application/json", #"application/json; charset=utf-8", nil];
return result;
}
// Generic error handling method.
- (void) presentError:(NSError *)error {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
}
Yup, that's a lot of code, and it should be broken into smaller methods - but it illustrates the logic that should be implemented.
The NSError you are getting now
In your comments you indicate that you are getting an NSError with the domain NSURLErrorDomain and code -1002. If you look at NSURLErrors.h, you will see that NSURL errors map to CFURL errors. If you look at CFNetworkErrors.h, you can see that error code -1002 is kCFURLErrorUnsupportedURL. The URL loading system thinks the URL you are using is not a supported type. This is most likely because the scheme of your URL is incorrect, or how you are attempting to pass credentials as part of the URL is incorrect. Elsewhere in your comments you indicate you are passing credentials as follows:
username:apikey:someurl.com/foo/
Which should be more like:
https://username:apikey#someurl.com/foo/
But only if the service you are accessing is using a supported HTTP authentication type (i.e. Basic authentication). Either way, correctly composing the URL will fix the error you are currently seeing.