How to correctly tear down multipeer connectivity session?
I reset the PeerID and Session on the Browser side whenever I get disconnected and want to reconnect:
// reset my PeerID. Sometimes when trying to reconnect to the same Advertiser with the same PeerID nothing happens
mcPeerID = [[MCPeerID alloc] initWithDisplayName:[UIDevice currentDevice].name];
mcSession = [[MCSession alloc] initWithPeer:mcPeerID];
mcSession.delegate = self;
mcNearbyServiceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:mcPeerID serviceType:kMCServiceType];
mcNearbyServiceBrowser.delegate = self;
[mcNearbyServiceBrowser startBrowsingForPeers];
On the Advertiser side, the connectedPeers count is decremented correctly on the disconnect, so reconnecting as a * different * browser doesn't seem to have a negative impact (maybe this isn't proper coding, but it works). With a new PeerID I can reconnect without issue. I reset the Session at the same time to keep them in sync (ie, I don't want a Session laying about that was init'd from the old PeerID).
MultipeerConnectivity - MCNearbyServiceBrowser constantly finding disconnected peers
I found a really helpful answer at the Apple Dev Forums.
Here the link.
Basically what resolved this issue was recycling the MCPeerID
. When the object is created I serialize it and stored in NSUserDefaults
. And anytime I need it back, like when I tear down the service and start it again I go to the stored object and used it instead of creating a new one.
You can find the next example code in the attached link above:
- (MCPeerID *)peerID {
if (!_peerID) {
_peerID = [MyClassName getRecycledPeerID];
}
return _peerID;
}
+ (MCPeerID *)getRecycledPeerID
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// if peer id exists, use that; else create one
if ([defaults objectForKey:kRECYCLED_PEER_ID]) {
NSData *peerIDData = [defaults dataForKey:kRECYCLED_PEER_ID];
return [NSKeyedUnarchiver unarchiveObjectWithData:peerIDData];
}
else {
return [[MCPeerID alloc] initWithDisplayName:[UIDevice currentDevice].name];
}
}
Reconnecting serveral peerConnections after page reload
Remove the TempDescriptions
global variable, and pass the sdp to getStream(offer.sdp)
directly.
Otherwise, you've socket.on('ask', function(offer){
called 4 times, overwriting TempDescriptions
. Then 3+ seconds later your 4 setTimeout
s roll around, all accessing the final value of TempDescriptions
only.
That's probably why only one RTCPeerConnection re-connects.
In general, using time delay to separate connections seems like a bad idea, as it slows down re-connection. Instead, why not send an id
? E.g.
socket.emit('ask', {id: connectionNumber++,
sdp: JSON.stringify(pc.localDescription),
user: loggedUserID,
fromSocket: ownSocket});
Update: Stop adding global variables to window
Whenever you assign to an undeclared variable like this:
connection = getPeerConnection();
...it creates a global on window
, e.g. window.connection
, and you have the same problem. You have 4 connection, but you're storing them in one variable.
Type "use strict";
at the head of your source file to catch this:
ReferenceError: assignment to undeclared variable connection
Scoping: The general problem
You're dealing with 4 connections here, but you lack an approach for scoping each instance.
Most other languages would tell you to create a class and make object instances, and put everything including connection
on this
. That's one good approach. In JS you can use closures instead. But at minimum you still need 4 variables holding the 4 connections, or an array of connections
. Then you look up—e.g. from the id
I mentioned—which connection to deal with.
Also, your try
/catch
es aren't going to catch asynchronous errors. Instead of defining all these callbacks, I strongly recommend using promises, or even async/await when dealing with the highly asynchronous WebRTC API. This makes scoping trivial. E.g.
const connections = [];
socket.on('ask', async ({user, id, sdp, fromSocket}) => {
try {
if (user != loggedUserID) return;
if (!connections[id]) {
connections[id] = getPeerConnection();
registerIceCandidate(connections[id]);
}
const connection = connections[id];
await connection.setRemoteDescription(JSON.parse(sdp));
await connection.setLocalDescription(await connection.createAnswer());
socket.emit('response', {sdp,
user: loggedUserID,
fromSocket: ownSocket,
toSocket: fromSocket});
} catch (err) {
console.log(err);
}
};
This way the error handling is solid.
Related Topics
iOS Floating Video Window Like Youtube App
How to Update Uibutton Title/Text Programmatically
iOS 9 Searchbar Disappears from Table Header View When Uisearchcontroller Is Active
Uitextview Disabling Text Selection
How to Detect If Code Is Running in Main App or App Extension Target
iOS Present Modal View Controller on Startup Without Flash
Xcode Info.Plist Build Variable ${Product_Name:Rfc1034Identifier} Seems Completely Undocumented
Xcode & Swift - Detecting User Touch of Uiview Inside of Uiscrollview
Download Xcode 4.2 Documentation for Offline Install
Linker Error in Xcode 5 for Libzbar.A
How to Detect Tableview Cell Touched or Clicked in Swift
Where Does the Indexpath of Dequeuereusablecellwithidentifier:Forindexpath: Get Used
Reload Tableview Section Without Scroll or Animation
Swift 3 How to Get Date for Tomorrow and Yesterday ( Take Care Special Case ) New Month or New Year
Memory-Mapped Files and Low-Memory Scenarios