I am using the MultipeerConeectivity framework to transmit data between two iOS devices. Sometimes the connection is not getting established even after the receiver accepts the invitation.
i.e - (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state is called with state as MCSessionStateNotConnected for sender.
Is there any workaround to get the reason of the connection failure, like dangling wifi, not in range, etc? Any help is greatly appreciated.
Related
I am using NSURLSessionDownloadTask and NSURLSession.
Question:
When I started downloading and after a while Internet is turned off, which method from or must be called ?
P.S.
At the moment, does not call the method.
I want get call in delegate’s method with error and display on screen some message -#“You lost internet connection”.
Be careful !
If you test on a simulator and disable wifi on a mac, the method will not be called.
Test only on a real device
Use
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
from NSURLSessionTaskDelegate.
And it's true - this method is called when Internet connection disappear only on the real device. On simulator task is paused and resume when Internet connection get back.
I'd like to use the Multipeer Connectivity functionality for my app. Brief intro to the functionality of the app:
The app should scan for other devices running the app (in background), connect to them and transfer a bit of data. All without interaction with the user.
Question: is it possible to connect to other devices using multipeer but without having to show the alert view that another device wants to connect and forcing the user to accept or decline the connection? Is there a way who I can programmatically accept all incoming connections from other devices? If so, how?
Thanks a lot in advance!
You have two questions here:
The app should scan for other devices running the app (in background)
The answer here is NO - MPC does not work in the background (see this so response)
For your second question:
is it possible to connect to other devices using multipeer but without having to show the alert
The answer is Yes .. all the detail you need can be found in the apple docs. Here's some snippets on what I do:
On one device - start the browser
_serviceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:_peerID
serviceType:_sessionName];
[_serviceBrowser startBrowsingForPeers];
On the other device - start the advertiser
_serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:_peerID
discoveryInfo:nil
serviceType:_sessionName];
[_serviceAdvertiser startAdvertisingPeer];
These services implement delegate functions to advise your app of a possible connection. Here the browser is advised of an advertiser and now invites the peer to join in a session
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
[browser invitePeer:peerID toSession:_session withContext:nil timeout:30.0];
}
The advertiser then responds
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer: (MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
invitationHandler(YES, _session);
}
Now you will receive a session delegate call to advise you of the state of your peer connectivity. At this point you should be connected - no "real" user interaction required.
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state
This question is going to be quite generic since I'm novice in iOS, video-streaming, and Bluetooth (going to be an interesting project).
Basically I wish to be able to stream low-res video from one iOS device to another iOS device, either through WiFi or Bluetooth depending on which one is available. Bonjour is used for initial service discovery. (I know streaming video over Bluetooth is non-ideal but it's one of the project's requirements)
So the question is what video-streaming framework/library can be used in order to maximize the amount of code shared between streaming video over WiFi and streaming video over Bluetooth.
Here are the instructions to test video streaming through Multipeer Conectivity:
You need Cocoapods, if you have not installed it yet, go to http://cocoapods.org/#install
Clone the transmitter from https://github.com/pj4533/AVCaptureMultipeerVideoDataOutput
Navigate to AVCaptureMultipeerVideoDataOutput/Sample directory in the Terminal and execute pod install
Clone the receiver from https://github.com/pj4533/SGSMultipeerVideoMixer
Run the transmitter in a physical device, you will see the back camera on screen
Run one or more receivers in the simulator or physical devices, the image of the emitter should appear in the receivers.
NOTE: The Multipeer Connectivity requires iOS 7 and both devices should have either WiFi or Bluetooth activated, I have tested it successfully on WiFi, Bluetooth may be too slow.
I will suggest to use the MultipeerConnectivity framework.
Here are the few delegate methods provided by MCSessionDelegate
MCSessionDelegate <NSObject>
// Received a byte stream from remote peer
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID;
// Start receiving a resource from remote peer
- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress;
// Finished receiving a resource from remote peer and saved the content in a temporary location - the app is responsible for moving the file to a permanent location within its sandbox
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error;`
Try to read http://nshipster.com/multipeer-connectivity/
I wrote code that does just that; here's a video I made of my app streaming video from one device to another:
<iframe width="560" height="315" src="https://www.youtube.com/embed/mWyZ1z55chw?rel=0" frameborder="0" gesture="media" allow="encrypted-media" allowfullscreen></iframe>
The screen recorder (iOS 11.2) is causing the occasional stutter on the receiving end (it was recording video at the same time my app was displaying it).
Anyway, it uses whatever connection you have between devices (wireless, Bluetooth, smoke signals, etc.l, let me know and I'll get you started.
Can't see the video? https://youtu.be/mWyZ1z55chw
I'm using the iOS 7 Multipeer framework in my app but I'm experiencing a problem with devices disconnecting. If I open the app in two devices: device A and device B the two devices connect to each other automatically. However, after several seconds device A disconnects from device B. i.e. At first the connection is like this:
A ---> B
A <--- B
After several seconds:
A ---> B
A B
Device A maintains it's connection but device B get's a MCSessionStateNotConnected.
This means that A can send data to B but B can't reply. I tried to get around this by checking if the device is connected and if it's not, re-initiating the connection using:
[browser invitePeer:peerID toSession:_session withContext:Nil timeout:10];
But the didChangeState callback just get's called with MCSessionStateNotConnected.
Strangely if I send app A to the background, then re-open it, B reconnects to it and the connection is maintained.
The Multipeer API (and documentation) seems a bit sparse so I was assuming that it would just work. In this situation how should I re-connect the device?
I was having the same problem, and it seems to have been related to my app browsing and advertising at the same time, and two invitations being sent/accepted. When I stopped doing this and let one peer defer to the other for invitations the devices stayed connected.
In my browser delegate I'm checking the hash value of the discovered peer's displayName and only sending an invitation if my peer has a higher hash value:
Edit
As pointed out by #Masa the hash value of an NSString will be different on 32 and 64 bit devices, so it's safer to use the compare: method on displayName.
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info {
NSLog(#"Browser found peer ID %#",peerID.displayName);
//displayName is created with [[NSUUID UUID] UUIDString]
BOOL shouldInvite = ([_myPeerID.displayName compare:peerID.displayName]==NSOrderedDescending);
if (shouldInvite){
[browser invitePeer:peerID toSession:_session withContext:nil timeout:1.0];
}
else {
NSLog(#"Not inviting");
}
}
As you say, the documentation is sparse so who knows what Apple really wants us to do, but I've experimented with both sending and accepting invitations using a single session, and also creating a new session for each invitation accepted/sent, but this particular way of doing things has given me the most success.
For anyone interested, I created MCSessionP2P, a demo app that illustrates the ad-hoc networking features of MCSession. The app both advertises itself on the local network and programmatically connects to available peers, establishing a peer-to-peer network. Hat tip to #ChrisH for his technique of comparing hash values for inviting peers.
I liked ChrisH's solution, which reveals the key insight that only one peer should connect to the other peer, not both. Mutual connection attempts results in mutual disconnection (though not that a single-sided connection actually is, counter-intuitively, a mutual connection in terms of status and communication, so that works fine).
However, I think a better approach than one peer inviting is for both peers to invite but only one peer to accept. I use this method now and it works great, because both peers have an opportunity to pass rich information to the other via the context parameter of the invitation, as opposed to having to rely on scant information available in the foundPeer delegate method.
Therefore, I recommend a solution like so:
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
[self invitePeer:peerID];
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void (^)(BOOL accept, MCSession *session))invitationHandler
{
NSDictionary *hugePackageOfInformation = [NSKeyedUnarchiver unarchiveObjectWithData:context];
BOOL shouldAccept = ([hugePackageOfInformation.UUID.UUIDString compare:self.user.UUID.UUIDString] == NSOrderedDescending);
invitationHandler(shouldAccept && ![self isPeerConnected:peerID], [self openSession]);
}
I have the same issue when devices trying to connect to each other at the same time and I don't know how to find a reason because we don't have any errors with MCSessionStateNotConnected.
We can use some crafty way to solve this issue:
Put into txt records ( discovery info ) a time [[NSDate date] timeIntervalSince1970] when app started. Who started first - send invitation to others.
But I think it's not a right way ( if apps start at the same time, unlikely... :) ). We need to figure out the reason.
This is the result of a bug, which I've reported to Apple. I've explained how to fix it in my response to another question: Why does my MCSession peer disconnect randomly?
I have not flagged these questions for merging, because while the underlying bug and solution are the same, the two questions describe different problems.
Save the hash of the peer B. Using a timer check the state of the connection continuously if is not connected try to reconnect with each given period of time.
According to apple document Choosing an inviter when using Multipeer Connectivity
“In iOS 7, sending simultaneous invites can cause both invites to fail, leaving both peers unable to communicate with each other.”
But iOS 8 has fixed it.
It seems that the .notConnected message is a false positive in that the device is still receiving data. So, I manually updated local connection state to .connected
It was hard to factor out other state from other examples. So, I wrote a bare bones MCSession example for SwiftUI, here: MultiPeer
I have been using GKMatch for quite a while successfully in an app. I have been chasing down and issue with the game occasionally stopping and have tracked it down to packets being sent but not received. This happens only occasionally but I can't seem to track down why it happens.
All messages are sent using GKSendDataReliable.
Logging has shown that the packet is being sent from one device successfully, but it is never received at the target device.
//Code sample of sending method....
//self.model.match is a GKMatch instance
-(BOOL) sendDataToAllPlayers:(NSData *)data error:(NSError **)error {
[self.model.debugger addToLog:#"GKManager - sending data"];
return [self.model.match sendDataToAllPlayers:data withDataMode:GKSendDataReliable error:error];
}
...
//Code sample of receiving method....
// The match received data sent from the player.
-(void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
[self.model.debugger addToLog:#"GKManager - received data"];
[super didReceiveData:data fromPlayer:playerID];
}
What I see happen is that periodically (maybe 1 in 100 messages) is sent without error from the 'sendDataToAllPlayers' method, but the receiving device never hits the 'didReceiveData' method. My understanding is that using GKSendDataReliable should send messages and then won't send another until it receives an acknowledgement. Messages aren't received but new messages are sent from the same device.
The sending method returns 'YES' and error is nil, but the didReceiveData is never hit...!
Has anyone ever seen this? Does anyone have any ideas what this could be? I don't know what else I could do to debug this.
I confirm the bug.
I made an example project consistently reproducing the issue: https://github.com/rabovik/GKMatchPacketLostExample. I tested it on a weak internet connection (iPad 3 with Wi-Fi and iPhone 4S with EDGE; both on iOS 6.1.3), and some packets regularly get lost without any error from Game Center API. Moreover sometimes device stops receiving any data, while another still sends and receives messages successfully.
So if we are sure the bug exists, the only possible workaround is to add an extra transport layer for truly reliable delivery.
I wrote a simple lib for this purpose: https://github.com/rabovik/RoUTP. It saves all sent messages until acknowledgement for each received, resends lost and buffers received messages in case of broken sequence.
In my tests combination "RoUTP + GKMatchSendDataUnreliable" works even beter than "RoUTP + GKMatchSendDataReliable" (and of course better than pure GKMatchSendDataReliable which is not really reliable).
[Edit: RoUTP no longer seems to work properly in iOS9]
I did some testing yesterday at the edge of my wifi's range where packet loss was occuring. What happens is that when packets are lost using GKMatchSendDataReliable the player is abruptly disconnected from the GKMatch session. match:player:didChangeState is called with GKPlayerStateDisconnected and the player's ID is removed from the playerIDs dictionary. This happens with only slight packet loss. I can still browse the internet from this connection for instance.
Now, if I switch to sending packets unreliably, then match:player:didChangeState never fires and the match keeps going without a problem (except losing the occasional packet which might be important). It will only disconnect if the packet loss becomes substantial. Now this is where Yan's RoUTP library is handy, since we can keep track of and retry these important messages without having our players disconnected when they encounter slight packet loss.
Also, data sending using GKMatchSendDataReliable will only return YES if the message has been successfully queued for delivery. It does not tell you whether or not the message was successfully delivered. How could it? It returns right away.