snapshot current state before gitea sync
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import Foundation
|
||||
import MultipeerConnectivity
|
||||
import UIKit
|
||||
|
||||
final class PeerSession: NSObject, ObservableObject {
|
||||
@Published private(set) var peers: [PeerInfo] = []
|
||||
@Published private(set) var isConnected: Bool = false
|
||||
@Published private(set) var statusText: String = "Idle"
|
||||
|
||||
var onMessage: ((SessionMessage) -> Void)?
|
||||
var onReceiveResource: ((String, URL) -> Void)?
|
||||
|
||||
private let serviceType = "runplus-sync"
|
||||
private let myPeerID = MCPeerID(displayName: UIDevice.current.name)
|
||||
|
||||
private var session: MCSession!
|
||||
private var advertiser: MCNearbyServiceAdvertiser?
|
||||
private var browser: MCNearbyServiceBrowser?
|
||||
|
||||
// Host keeps per-peer offsets
|
||||
private var peerOffsets: [MCPeerID: TimeInterval] = [:]
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
session = MCSession(peer: myPeerID, securityIdentity: nil, encryptionPreference: .required)
|
||||
session.delegate = self
|
||||
}
|
||||
|
||||
func startHosting() {
|
||||
stop()
|
||||
statusText = "Hosting"
|
||||
advertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: nil, serviceType: serviceType)
|
||||
advertiser?.delegate = self
|
||||
advertiser?.startAdvertisingPeer()
|
||||
}
|
||||
|
||||
func startJoining() {
|
||||
stop()
|
||||
statusText = "Browsing"
|
||||
browser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: serviceType)
|
||||
browser?.delegate = self
|
||||
browser?.startBrowsingForPeers()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
advertiser?.stopAdvertisingPeer()
|
||||
advertiser = nil
|
||||
browser?.stopBrowsingForPeers()
|
||||
browser = nil
|
||||
session.disconnect()
|
||||
peers = []
|
||||
isConnected = false
|
||||
statusText = "Idle"
|
||||
peerOffsets = [:]
|
||||
}
|
||||
|
||||
func broadcast(_ message: SessionMessage) {
|
||||
guard !session.connectedPeers.isEmpty else { return }
|
||||
send(message, to: session.connectedPeers)
|
||||
}
|
||||
|
||||
func reply(_ message: SessionMessage) {
|
||||
guard !session.connectedPeers.isEmpty else { return }
|
||||
send(message, to: session.connectedPeers)
|
||||
}
|
||||
|
||||
func updateOffset(hostUptime: TimeInterval, peerUptime: TimeInterval) {
|
||||
for peer in session.connectedPeers {
|
||||
let offset = hostUptime - peerUptime
|
||||
peerOffsets[peer] = offset
|
||||
}
|
||||
}
|
||||
|
||||
func sendTrack(_ track: LocalTrack) {
|
||||
guard !session.connectedPeers.isEmpty else { return }
|
||||
let info = TrackInfoPayload(trackID: track.id.uuidString, displayName: track.displayName)
|
||||
send(.trackInfo(info), to: session.connectedPeers)
|
||||
for peer in session.connectedPeers {
|
||||
let resourceName = "track:\(track.id.uuidString)"
|
||||
session.sendResource(at: track.url, withName: resourceName, toPeer: peer) { [weak self] error in
|
||||
if let error {
|
||||
DispatchQueue.main.async {
|
||||
self?.statusText = "Send track failed: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func send(_ message: SessionMessage, to peers: [MCPeerID]) {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(message)
|
||||
try session.send(data, toPeers: peers, with: .reliable)
|
||||
} catch {
|
||||
statusText = "Send failed: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
private func handle(_ data: Data, from peerID: MCPeerID) {
|
||||
do {
|
||||
let message = try JSONDecoder().decode(SessionMessage.self, from: data)
|
||||
if case .syncRequest(let hostUptime) = message {
|
||||
let peerUptime = SyncClock.uptime()
|
||||
SyncClock.setHostOffset(hostUptime: hostUptime, peerUptime: peerUptime)
|
||||
}
|
||||
onMessage?(message)
|
||||
} catch {
|
||||
statusText = "Decode failed: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerSession: MCSessionDelegate {
|
||||
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
|
||||
DispatchQueue.main.async {
|
||||
self.peers = session.connectedPeers.map { PeerInfo(id: $0.displayName, name: $0.displayName) }
|
||||
self.isConnected = !session.connectedPeers.isEmpty
|
||||
switch state {
|
||||
case .connected:
|
||||
self.statusText = "Connected to \(peerID.displayName)"
|
||||
self.send(.hello, to: [peerID])
|
||||
case .connecting:
|
||||
self.statusText = "Connecting to \(peerID.displayName)"
|
||||
case .notConnected:
|
||||
self.statusText = "Disconnected"
|
||||
@unknown default:
|
||||
self.statusText = "Unknown state"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
|
||||
DispatchQueue.main.async {
|
||||
self.handle(data, from: peerID)
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {}
|
||||
|
||||
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {}
|
||||
|
||||
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
|
||||
guard error == nil, let localURL else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.onReceiveResource?(resourceName, localURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerSession: MCNearbyServiceAdvertiserDelegate {
|
||||
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
|
||||
invitationHandler(true, session)
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerSession: MCNearbyServiceBrowserDelegate {
|
||||
func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
|
||||
browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
|
||||
}
|
||||
|
||||
func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {}
|
||||
}
|
||||
Reference in New Issue
Block a user