smithery/co-labs-co

multipeerconnectivity-p2p-streaming

Peer-to-peer video streaming using Apple's MultipeerConnectivity framework.

Installation

$ npx skills add smithery/co-labs-co --skill multipeerconnectivity-p2p-streaming

Summary

  • Peer-to-peer video streaming using Apple's MultipeerConnectivity framework.
  • Use this skill when building local mesh networking apps, video streaming between devices (baby monitors, security cameras), local multiplayer games without servers, or AirDrop-like file sharing with custom protocols.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/co-labs-co.

npx skills add smithery/co-labs-co

Browse all from smithery/co-labs-co

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version0.1.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,794 B
  • docs SUMMARY.md 335 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Skill: MultipeerConnectivity P2P Streaming

Peer-to-peer video streaming using Apple's MultipeerConnectivity framework with per-viewer session architecture, trust-based authentication, and reliable video frame delivery.

When to Use

  • Local mesh networking: Connecting 2-8 devices without internet/server infrastructure
  • Video streaming between devices: Baby monitors, security cameras, remote displays
  • Game sessions: Local multiplayer without server requirements
  • File/data sharing: AirDrop-like functionality with custom protocols
  • Proximity-based features: Nearby device discovery and communication

Consider Network.framework instead when:

  • You have a clear client/server model (not true peer-to-peer)
  • You need lower latency for real-time applications (per TN3151)
  • You require fine-grained control over transport protocols

Key Concepts

Core Components

Component Role Description
MCPeerID Identity Unique device identifier (MUST be persisted for reconnection)
MCSession Connection Manages peer connections and data transfer
MCNearbyServiceAdvertiser Discovery Advertises availability to nearby peers
MCNearbyServiceBrowser Discovery Searches for advertising peers

Bonjour Service Type Naming (RFC 6763)

Format: _<service>._<protocol>
Rules:
- Maximum 15 characters (excluding underscores)
- Lowercase letters, numbers, hyphens only
- Must not start or end with hyphen

Examples:
✅ "carseet-stream"   (valid: 14 chars)
✅ "myapp-share"      (valid: 11 chars)  
❌ "my-really-long-service-name" (invalid: > 15 chars)

Per-Viewer Session Architecture

The recommended pattern uses separate MCSession per connected peer rather than a single shared session:

┌─────────────────────────────────────────────────────┐
│                   STREAMER (Camera)                  │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────┐ │
│  │ MCSession A  │  │ MCSession B  │  │ MCSession C│ │
│  │ (Viewer 1)   │  │ (Viewer 2)   │  │ (Viewer 3) │ │
│  └──────┬───────┘  └──────┬───────┘  └──────┬─────┘ │
└─────────┼─────────────────┼─────────────────┼───────┘
          │                 │                 │
          ▼                 ▼                 ▼
     ┌─────────┐       ┌─────────┐       ┌─────────┐
     │ VIEWER 1│       │ VIEWER 2│       │ VIEWER 3│
     └─────────┘       └─────────┘       └─────────┘

Benefits:

  • Selective disconnection: Revoke one viewer without affecting others
  • Per-viewer state tracking: Independent connection status per device
  • Granular access control: Different trust levels per connection

Encryption Options

MCSession(
    peer: peerID,
    securityIdentity: nil,
    encryptionPreference: .required // .none, .optional, .required
)
Preference Use Case
.required Sensitive data (video, personal info) - Recommended
.optional Balanced (encrypts if peer supports)
.none Maximum performance (not recommended)

Implementation Guide

Step 1: MCPeerID Persistence

MCPeerID instances with the same displayName are NOT equal unless archived/unarchived:

import KeychainAccess

class P2PStreamingManager {
    private let keychain = Keychain(service: Bundle.main.bundleIdentifier ?? "com.app")
        .accessibility(.whenUnlockedThisDeviceOnly)
    private let peerIDKey = "p2p_mcpeer_id"
    
    private func setupPeerID() {
        if let persistedPeerID = loadPersistedPeerID() {
            peerID = persistedPeerID
            return
        }
        
        let newPeerID = MCPeerID(displayName: UIDevice.current.name)
        peerID = newPeerID
        persistPeerID(newPeerID)
    }
    
    private func loadPersistedPeerID() -> MCPeerID? {
        guard let data = keychain[data: peerIDKey] else { return nil }
        return try? NSKeyedUnarchiver.unarchivedObject(
            ofClass: MCPeerID.self, 
            from: data
        )
    }
}

Step 2: Streamer (Advertiser) Setup

class StreamerManager {
    private var peerID: MCPeerID!
    private var advertiser: MCNearbyServiceAdvertiser?
    private var viewerSessions: [String: MCSession] = []  // Per-viewer sessions
    
    private let serviceType = "carseet-stream"  // Max 15 chars
    
    func startAdvertising() {
        let discoveryInfo: [String: String] = [
            "role": "streamer",
            "deviceId": deviceId
        ]
        
        advertiser = MCNearbyServiceAdvertiser(
            peer: peerID,
            discoveryInfo: discoveryInfo,
            serviceType: serviceType
        )
        advertiser?.delegate = self
        advertiser?.startAdvertisingPeer()
    }
}

extension StreamerManager: MCNearbyServiceAdvertiserDelegate {
    func advertiser(_ advertiser: MCNearbyServiceAdvertiser,
                    didReceiveInvitationFromPeer peerID: MCPeerID,
                    withContext context: Data?,
                    invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        
        // Per-viewer session architecture
        let viewerSession = MCSession(
            peer: self.peerID,
            securityIdentity: nil,
            encryptionPreference: .required
        )
        viewerSession.delegate = self
        
        // Store session for selective disconnect
        viewerSessions[credential.deviceId] = viewerSession
        
        invitationHandler(true, viewerSession)
    }
}

Step 3: Reliable Video Frame Streaming

extension StreamerManager {
    func sendEncodedFrame(_ frame: EncodedVideoFrame) {
        let serializedData = frame.serialize()
        
        for (_, viewerSession) in viewerSessions {
            guard !viewerSession.connectedPeers.isEmpty else { continue }
            
            do {
                // Use .unreliable for video (lower latency, drops OK)
                // Use .reliable for control messages
                try viewerSession.send(
                    serializedData,
                    toPeers: viewerSession.connectedPeers,
                    with: .unreliable
                )
            } catch {
                print("Frame send error: \(error)")
            }
        }
    }
}

Step 4: Background Handling (Critical)

MPC does not function in background:

class P2PManager {
    func setupAppLifecycleObservers() {
        NotificationCenter.default.addObserver(
            forName: UIApplication.didEnterBackgroundNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            // MUST disconnect - MPC fails in background
            for (_, session) in self?.viewerSessions ?? [:] {
                session.disconnect()
            }
            self?.viewerSessions.removeAll()
        }
    }
}

Common Pitfalls

1. MCPeerID Equality Trap

// ❌ WRONG - These are NOT equal even with same name
let peer1 = MCPeerID(displayName: "Device")
let peer2 = MCPeerID(displayName: "Device")
peer1 == peer2  // FALSE!

// ✅ CORRECT - Archive and persist, then unarchive
let archived = try NSKeyedArchiver.archivedData(withRootObject: peer1, requiringSecureCoding: true)
let restored = try NSKeyedUnarchiver.unarchivedObject(ofClass: MCPeerID.self, from: archived)
peer1 == restored  // TRUE!

2. Service Type Length Violation

// ❌ WRONG - Too long (18 chars)
let serviceType = "my-awesome-service"  // Will fail silently

// ✅ CORRECT - Max 15 characters
let serviceType = "carseet-stream"  // 14 chars

3. Background Session Corruption

// ❌ WRONG - Trying to keep session alive in background
func applicationDidEnterBackground() {
    // MPC WILL cause undefined behavior
}

// ✅ CORRECT - Disconnect immediately on background
func applicationDidEnterBackground() {
    session.disconnect()
}

4. Shared Session Disconnect Side Effects

// ❌ WRONG - Shared session affects all peers
session.disconnect()  // Drops ALL connected viewers

// ✅ CORRECT - Per-viewer sessions for selective disconnect
func disconnectViewer(deviceId: String) {
    viewerSessions[deviceId]?.disconnect()
    viewerSessions.removeValue(forKey: deviceId)
}

References


Derived from CarSeet project - P2PStreamingManager.swift