Reputation: 967
I currently have two devices connected to each other over the multi-peer connectivity framework. When connected each device will instantly send a packet of nsdata to each other, in this first instance the data will contain a sound and image. This appears to work fine and both user receive the relevant data.
The users can now select each other from the view and send another bit of data. However i'm seeing that if the host (the client who sent the invitation) attempts to send some more data the following error is displayed in the console.
Peers (
PeerName
) not connected
But if the client (the invitee) sends data the it arrives as expected on the host. What is especially interesting is the fact that i have NSLogs in the:
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification
Method which fire when a disconnect occurs, there are no disconnects occurring and the devices are connected to one another. My code is below, does anyone have any idea why this might occur?
#pragma mark Send Buzz Notification
#pragma mark -
-(void)sendBuzzNotification{
// create the user (remove the image to lower size)
LocalUserClass *withoutImage = [[LocalUserClass alloc]initWithName:[[[GlobalData sharedGlobalData]localUser]personName] personImage:nil personSound:[[[GlobalData sharedGlobalData]localUser]personSound] personPeerId:nil];
// send the users sound and name to the other peer/s
NSData *dataToSend = [NSKeyedArchiver archivedDataWithRootObject:withoutImage];
NSError *error;
// skip if no users are selcted
if ([selectedUsers count] == 0) {
return;
}
else{
// create the array of peer Ids from selected users
NSMutableArray *tempArray = [[NSMutableArray alloc]init];
for (LocalUserClass *object in selectedUsers) {
// add the peer id
MCPeerID *theId = object.personPeerId;
[tempArray addObject:theId];
}
NSArray *theUsers = [[NSArray alloc]init];
theUsers = tempArray;
// send the data
[appDelegate.mcManager.session sendData:dataToSend
toPeers:theUsers
withMode:MCSessionSendDataReliable
error:&error];
if (error) {
NSLog(@"%@", [error localizedDescription]);
}
// vibrate the device to show its been sent
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
}
#pragma mark Peer Connected State Checker
#pragma mark -
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
MCPeerID *peerID = [[notification userInfo] objectForKey:@"peerID"];
//NSLog(@"%@",peerID.displayName);
MCSessionState state = [[[notification userInfo] objectForKey:@"state"] intValue];
LocalUserClass *theUser = [[LocalUserClass alloc]initWithName:peerID.displayName personImage:nil personSound:nil personPeerId:peerID];
//NSLog(@"the user name is: %@ the user sound is: %@ the user peer id is %@",theUser.personName,theUser.personSound,theUser.personPeerId);
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
// add the user
[connectedUsers addObject:theUser];
// send the users details to the newly connected peer (profile pic and sound)
[self sendUserDetailsToPeer:peerID];
}
else if (state == MCSessionStateNotConnected){
// do we have connections
if ([connectedUsers count] > 0) {
// get the user to remove
NSInteger thePeerIndex = 0;
NSInteger thePeerIndexSelectedUsers = 0;
for (LocalUserClass *object in connectedUsers) {
if (object.personPeerId == theUser.personPeerId) {
thePeerIndex = [connectedUsers indexOfObject:object];
[connectedUsers removeObjectAtIndex:thePeerIndex];
}
}
for (LocalUserClass *user in selectedUsers) {
if (user.personPeerId == theUser.personPeerId) {
thePeerIndexSelectedUsers = [selectedUsers indexOfObject:user];
[selectedUsers removeObjectAtIndex:thePeerIndexSelectedUsers];
}
}
}
}
}
// push to main queue for speedy response
dispatch_async(dispatch_get_main_queue(), ^(void) {
// create user playback devices
[playbackManager createUserAudioPlayers:connectedUsers withSound:nil];
[collView reloadData];
[self populateUserBox];
BOOL peersExist = ([[appDelegate.mcManager.session connectedPeers] count] == 0);
//NSLog(@"PEER COUNT IS %lu",(unsigned long)[[appDelegate.mcManager.session connectedPeers] count]);
[disconnectButton setEnabled:!peersExist];
if ([disconnectButton isEnabled]) {
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(51/255.0) green:(202/255.0) blue:(168/255.0) alpha:1.0]];
}
else{
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(107/255.0) green:(107/255.0) blue:(107/255.0) alpha:1.0]];
}
});
}
#pragma mark Peer Received Data
#pragma mark -
-(void)didReceiveDataWithNotification:(NSNotification *)notification{
NSData *receivedData = [[notification userInfo] objectForKey:@"data"];
receivedUser = [NSKeyedUnarchiver unarchiveObjectWithData:receivedData];
CGImageRef cgref = [receivedUser.personImage CGImage];
CIImage *cim = [receivedUser.personImage CIImage];
// are we getting a whole user or just the user sound and name (check for no image)
if (cim == nil && cgref == NULL)
{
// recieve the data (TODO probably needs to be run in the background)
[self performSelectorOnMainThread:@selector(recieveBuzzNotification) withObject:nil waitUntilDone:YES];
}
else{
// recieve the data (TODO probably needs to be run in the background)
[self performSelectorOnMainThread:@selector(receivedUserProfile) withObject:nil waitUntilDone:YES];
}
}
UPDATE: The host appears to lose connection to the client after the inital data transfer:
[self sendUserDetailsToPeer:peerID];
However the disconnection state doesn't fire anything, and the client can still send data to the host. this is seen by logging out the connected sessions:
session.connectedPeers
EDIT: The additional code from the view controller:
#pragma mark Create Session
#pragma mark -
-(void)setupSession{
// create session and peer with details from the user
[appDelegate.mcManager setupPeerAndSessionWithDisplayName:[[[GlobalData sharedGlobalData]localUser]personName]];
[appDelegate.mcManager advertiseSelf:visibleSwitch.isOn];
appDelegate.mcManager.browser.delegate = self;
// set global user peerId
[[[GlobalData sharedGlobalData]localUser]setPersonPeerId:appDelegate.mcManager.peerIDGlobal];
// setup notification to track the user info of connected peers
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(peerDidChangeStateWithNotification:)
name:@"MCDidChangeStateNotification"
object:nil];
// notifcation to track recieved data
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveDataWithNotification:)
name:@"MCDidReceiveDataNotification"
object:nil];
}
and the MCManager:
-(id)init{
self = [super init];
if (self) {
peerIDGlobal = nil;
session = nil;
browser = nil;
advertiser = nil;
}
return self;
}
//----CUSTOM---------------------------------------------------------------------------------------------------------------//
//-------------------------------------------------------------------------------------------------------------------------//
//----PUBLIC---------------------------------------------------------------------------------------------------------------//
#pragma mark Setup Peer and Session
#pragma mark -
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{
peerIDGlobal = [[MCPeerID alloc] initWithDisplayName:[[[GlobalData sharedGlobalData]localUser]personName]];
session = [[MCSession alloc] initWithPeer:peerIDGlobal];
session.delegate = self;
}
#pragma mark Setup Browser
#pragma mark -
-(void)setupMCBrowser{
browser = [[MCBrowserViewController alloc] initWithServiceType:@"chat-files" session:session];
}
#pragma mark Setup Advertiser
#pragma mark -
-(void)advertiseSelf:(BOOL)shouldAdvertise{
if (shouldAdvertise) {
advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:@"chat-files"
discoveryInfo:nil
session:session];
[advertiser start];
}
else{
[advertiser stop];
advertiser = nil;
}
}
//----DELEGATES------------------------------------------------------------------------------------------------------------//
//-------------------------------------------------------------------------------------------------------------------------//
#pragma mark Session delegate
#pragma mark -
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
// create the dict with connected user info
NSDictionary *dict = @{@"peerID": peerID,
@"state" : [NSNumber numberWithInt:state]
};
// post the notification for tracking
[[NSNotificationCenter defaultCenter] postNotificationName:@"MCDidChangeStateNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSDictionary *dict = @{@"data": data,
@"peerID": peerID
};
[[NSNotificationCenter defaultCenter] postNotificationName:@"MCDidReceiveDataNotification"
object:nil
userInfo:dict];
}
Upvotes: 1
Views: 2539
Reputation: 4558
Multipeer Connectivity has known problems when two devices browse/advertise and send/accept invitations at the same time.
The solution that Apple recommended during WWDC 14 is the one I posted here: Reconnecting to disconnected peers which uses a deterministic approach to deciding which peer in a pair accepts the other's invitation.
It seems that MCBrowserViewController
and MCAdvertiserAssistant
can only be reliably used in apps where one peer is browsing with the other(s) advertising.
Upvotes: 0
Reputation: 967
Thanks Chris, this looks to be the issue, i've rewritten the code based on some boilerplate code from here: https://github.com/shrtlist/MCSessionP2P
Now the app automatically connects to users without browsers, it's a great solution and i highly recommend anyone with problems have a look at the code.
Upvotes: 3
Reputation: 353
I was having a similar problem, when devices were both advertising and browsing simultaneously. I used this method as suggested (I believe) in an Apple WWDC video to make sure that there wasn't a duplicate connection;
func browser(browser: MCNearbyServiceBrowser!, foundPeer peerID: MCPeerID!, withDiscoveryInfo info: [NSObject : AnyObject]!) {
if (self.myPeerID.hash > peerID.hash){
println("foundPeer:\(peerID.description) -> invitePeer")
mpBrowser.invitePeer(peerID, toSession: self.mpSession, withContext: nil, timeout: peerInviteTimeout)
}else{
println("foundPeer: \(peerID.description) hash is higher->defer invite")
}
}
Upvotes: -1