Tuesday, July 9, 2024
HomeiOS Developmentswift - Sharing Display Seize Disconnects MultipeerConnectivity over WebRTC Throughout iOS Units

swift – Sharing Display Seize Disconnects MultipeerConnectivity over WebRTC Throughout iOS Units


I am constructing a prototype app that showcases the performance on iOS to seize a tool’s display screen, join to a different system, after which video stream this (as excessive res and low latency) over a neighborhood WiFi community. The chosen lib I am implementing this with is WebRTC (open to different ideas).

I load the app onto 2 units, I broadcast the display screen of 1 system, it seems from the logs to hook up with the opposite system I’ve set to obtain, however after that they seem to drop reference to “[GCKSession] Not in linked state, so giving up for participant [xxxxxxxx] on channel [x]” on 5 channel.

I’ve included the complete code you’ll be able to run in Xcode beneath together with a console from the connecting system over the last construct and dropped connection.

I am at a irritating DEAD END.

WebRTCManager.swift

import Basis
import WebRTC
import ReplayKit
import MultipeerConnectivity

class WebRTCManager: NSObject, ObservableObject {
    non-public var peerConnection: RTCPeerConnection?
    @Printed var localVideoTrack: RTCVideoTrack?
    @Printed var remoteVideoTrack: RTCVideoTrack?
    non-public var peerConnectionFactory: RTCPeerConnectionFactory?
    non-public var videoSource: RTCVideoSource?
    non-public var videoCapturer: RTCVideoCapturer?
    
    non-public var peerID: MCPeerID
    non-public var session: MCSession
    non-public var advertiser: MCNearbyServiceAdvertiser?
    non-public var browser: MCNearbyServiceBrowser?
    
    @Printed var connectedPeers: [MCPeerID] = []
    @Printed var localSDP: String = ""
    @Printed var localICECandidates: [String] = []
    
    @Printed var isBroadcasting: Bool = false
    @Printed var remoteTrackAdded: Bool = false
    
    non-public var isConnected = false

    override init() {
        RTCInitializeSSL()
        peerConnectionFactory = RTCPeerConnectionFactory()
        
        peerID = MCPeerID(displayName: UIDevice.present.title)
        session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none)
        
        tremendous.init()
        
        session.delegate = self
    }
    
    func startBroadcasting() {
        isBroadcasting = true
        advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: "screen-share")
        advertiser?.delegate = self
        advertiser?.startAdvertisingPeer()
        
        setupPeerConnection()
        setupVideoSource()
        startScreenCapture()
    }

    func startReceiving() {
        browser = MCNearbyServiceBrowser(peer: peerID, serviceType: "screen-share")
        browser?.delegate = self
        browser?.startBrowsingForPeers()
        
        setupPeerConnection()
    }
    
    non-public func setupPeerConnection() {
        let configuration = RTCConfiguration()
        configuration.iceServers = [RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"])]
        configuration.sdpSemantics = .unifiedPlan
        let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
        peerConnection = peerConnectionFactory?.peerConnection(with: configuration, constraints: constraints, delegate: self)
        
        // Guarantee peer connection state is checked earlier than continuing
        guard let peerConnection = peerConnection else {
            print("Didn't create peer connection")
            return
        }
        
        peerConnection.delegate = self
    }
    
    non-public func setupVideoSource() {
        videoSource = peerConnectionFactory?.videoSource()
        
        #if targetEnvironment(simulator)
        videoCapturer = RTCFileVideoCapturer(delegate: videoSource!)
        #else
        videoCapturer = RTCCameraVideoCapturer(delegate: videoSource!)
        #endif
        
        localVideoTrack = peerConnectionFactory?.videoTrack(with: videoSource!, trackId: "video0")
        
        if let localVideoTrack = localVideoTrack {
            peerConnection?.add(localVideoTrack, streamIds: ["stream0"])
            print("Native video observe added to look connection")
        } else {
            print("Didn't create native video observe")
        }
    }
    
    non-public func startScreenCapture() {
        let recorder = RPScreenRecorder.shared()
        recorder.startCapture { [weak self] (sampleBuffer, sort, error) in
            guard let self = self else { return }
            
            if let error = error {
                print("Error beginning display screen seize: (error)")
                return
            }
            
            if sort == .video {
                self.processSampleBuffer(sampleBuffer, with: sort)
            }
        } completionHandler: { error in
            if let error = error {
                print("Error in display screen seize completion: (error)")
            } else {
                print("Display seize began efficiently")
            }
        }
    }
    
    non-public func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
        change sampleBufferType {
        case .video:
            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
            
            let timestamp = NSDate().timeIntervalSince1970 * 1000
            let videoFrame = RTCVideoFrame(buffer: RTCCVPixelBuffer(pixelBuffer: pixelBuffer),
                                           rotation: ._0,
                                           timeStampNs: Int64(timestamp))
            
            self.videoSource?.capturer(self.videoCapturer!, didCapture: videoFrame)
            
        default:
            break
        }
    }
    
    func stopBroadcasting() {
        isBroadcasting = false
        advertiser?.stopAdvertisingPeer()
        advertiser = nil
        stopScreenCapture()
        closePeerConnection()
    }

    func stopReceiving() {
        browser?.stopBrowsingForPeers()
        browser = nil
        closePeerConnection()
    }
    
    non-public func stopScreenCapture() {
        RPScreenRecorder.shared().stopCapture { [weak self] error in
            if let error = error {
                print("Error stopping display screen seize: (error)")
            } else {
                print("Display seize stopped efficiently")
                DispatchQueue.foremost.async {
                    self?.localVideoTrack = nil
                }
            }
        }
    }
    
    non-public func closePeerConnection() {
        guard let peerConnection = peerConnection else { return }
        
        if isConnected {
            // Correctly deal with disconnection if linked
            peerConnection.shut()
        }
        self.peerConnection = nil
        DispatchQueue.foremost.async {
            self.remoteVideoTrack = nil
            self.localSDP = ""
            self.localICECandidates.removeAll()
        }
        print("Peer connection closed")
    }
    
    non-public func createOffer() {
        print("Creating provide")
        let constraints = RTCMediaConstraints(mandatoryConstraints: [
            "OfferToReceiveVideo": "true",
            "OfferToReceiveAudio": "false"
        ], optionalConstraints: nil)
        peerConnection?.provide(for: constraints) { [weak self] sdp, error in
            guard let self = self, let sdp = sdp else {
                print("Didn't create provide: (error?.localizedDescription ?? "unknown error")")
                return
            }
            self.peerConnection?.setLocalDescription(sdp) { error in
                if let error = error {
                    print("Error setting native description: (error)")
                } else {
                    print("Native description (provide) set efficiently")
                    self.sendSDP(sdp)
                }
            }
        }
    }

    non-public func createAnswer() {
        print("Creating reply")
        let constraints = RTCMediaConstraints(mandatoryConstraints: [
            "OfferToReceiveVideo": "true",
            "OfferToReceiveAudio": "false"
        ], optionalConstraints: nil)
        peerConnection?.reply(for: constraints) { [weak self] sdp, error in
            guard let self = self, let sdp = sdp else {
                print("Didn't create reply: (error?.localizedDescription ?? "unknown error")")
                return
            }
            self.peerConnection?.setLocalDescription(sdp) { error in
                if let error = error {
                    print("Error setting native description: (error)")
                } else {
                    print("Native description (reply) set efficiently")
                    self.sendSDP(sdp)
                }
            }
        }
    }
    
    non-public func setRemoteDescription(_ sdp: RTCSessionDescription) {
        peerConnection?.setRemoteDescription(sdp) { error in
            if let error = error {
                print("Error setting distant description: (error)")
            } else {
                print("Distant description set efficiently")
            }
        }
    }
    
    non-public func addIceCandidate(_ candidate: RTCIceCandidate) {
        guard let peerConnection = peerConnection, isConnected else {
            print("Can not add ICE candidate, peer connection will not be linked")
            return
        }
        peerConnection.add(candidate)
        print("ICE candidate added")
    }
    
    non-public func sendSDP(_ sdp: RTCSessionDescription) {
        let dict: [String: Any] = ["type": sdp.type.rawValue, "sdp": sdp.sdp]
        if let information = strive? JSONSerialization.information(withJSONObject: dict, choices: []) {
            do {
                strive session.ship(information, toPeers: session.connectedPeers, with: .dependable)
                print("SDP despatched to friends")
            } catch {
                print("Didn't ship SDP: (error)")
            }
        }
    }
}

extension WebRTCManager: MCSessionDelegate, MCNearbyServiceAdvertiserDelegate, MCNearbyServiceBrowserDelegate {

    func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        DispatchQueue.foremost.async {
            self.connectedPeers = session.connectedPeers
            change state {
            case .linked:
                print("Peer linked: (peerID.displayName)")
                self.browser?.stopBrowsingForPeers()
                self.advertiser?.stopAdvertisingPeer()
                self.isConnected = true  // Set isConnected to true right here
                if self.isBroadcasting {
                    self.createOffer()
                }
            case .connecting:
                print("Peer connecting: (peerID.displayName)")
            case .notConnected:
                print("Peer not linked: (peerID.displayName)")
                self.isConnected = false
            @unknown default:
                print("Unknown state: (peerID.displayName)")
            }
        }
    }
    
    func session(_ session: MCSession, didReceive information: Information, fromPeer peerID: MCPeerID) {
        let dict = strive? JSONSerialization.jsonObject(with: information, choices: []) as? [String: Any]
        if let typeInt = dict?["type"] as? Int, let sdp = dict?["sdp"] as? String,
           let sort = RTCSdpType(rawValue: typeInt) {
            let rtcSdp = RTCSessionDescription(sort: sort, sdp: sdp)
            
            self.peerConnection?.setRemoteDescription(rtcSdp) { [weak self] error in
                if let error = error {
                    print("Error setting distant description: (error)")
                } else {
                    print("Distant description set efficiently")
                    if sort == .provide {
                        self?.createAnswer()
                    }
                }
            }
        } else if let sdp = dict?["candidate"] as? String,
                  let sdpMid = dict?["sdpMid"] as? String,
                  let sdpMLineIndexString = dict?["sdpMLineIndex"] as? String,
                  let sdpMLineIndex = Int32(sdpMLineIndexString) {
            let candidate = RTCIceCandidate(sdp: sdp, sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid)
            self.peerConnection?.add(candidate)
        }
    }
    
    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?) {}
    
    func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Information?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        invitationHandler(true, session)
    }
    
    func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo data: [String : String]?) {
        browser.invitePeer(peerID, to: session, withContext: nil, timeout: 30)
    }
    
    func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {}
}

extension WebRTCManager: RTCPeerConnectionDelegate {
    func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
        print("Signaling state modified: (stateChanged.rawValue)")
    }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
        print("Stream added with ID: (stream.streamId)")
        if let videoTrack = stream.videoTracks.first {
            print("Video observe added: (videoTrack.trackId)")
            DispatchQueue.foremost.async {
                self.remoteVideoTrack = videoTrack
                self.remoteTrackAdded = true
                self.objectWillChange.ship()
                print("Distant video observe set")
            }
        } else {
            print("No video tracks within the stream")
        }
    }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
        print("Stream eliminated")
    }
    
    func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
        print("Negotiation wanted")
    }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
        print("ICE connection state modified: (newState.rawValue)")
        change newState {
        case .checking, .linked, .accomplished:
            print("ICE linked")
            self.isConnected = true
        case .failed, .disconnected, .closed:
            print("ICE connection failed or closed")
            self.isConnected = false
            // Deal with reconnection or cleanup if mandatory
        case .new:
            print("New ICE connection")
        case .rely:
            print("ICE rely")
        @unknown default:
            print("Unknown ICE state")
        }
    }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
        print("ICE gathering state modified: (newState.rawValue)")
    }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
         print("ICE candidate generated: (candidate.sdp)")
         DispatchQueue.foremost.async {
             self.localICECandidates.append(candidate.sdp)
         }
         
         // All the time ship ICE candidates
         let dict: [String: Any] = ["candidate": candidate.sdp, "sdpMid": candidate.sdpMid ?? "", "sdpMLineIndex": candidate.sdpMLineIndex]
         if let information = strive? JSONSerialization.information(withJSONObject: dict, choices: []) {
             do {
                 strive session.ship(information, toPeers: session.connectedPeers, with: .dependable)
                 print("ICE candidate despatched to friends")
             } catch {
                 print("Didn't ship ICE candidate: (error)")
             }
         }
     }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
        print("Eliminated ICE candidates")
    }
    
    func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
        print("Information channel opened")
    }
}

extension Notification.Identify {
    static let remoteVideoTrackAdded = Notification.Identify("remoteVideoTrackAdded")
}

RTCVideoView.swift

import SwiftUI
import WebRTC

struct RTCVideoView: UIViewRepresentable {
    @ObservedObject var webRTCManager: WebRTCManager
    var isLocal: Bool

    func makeUIView(context: Context) -> RTCMTLVideoView {
        let videoView = RTCMTLVideoView(body: .zero)
        videoView.videoContentMode = .scaleAspectFit
        updateVideoTrack(videoView)
        return videoView
    }

    func updateUIView(_ uiView: RTCMTLVideoView, context: Context) {
        updateVideoTrack(uiView)
    }

    non-public func updateVideoTrack(_ uiView: RTCMTLVideoView) {
        if isLocal {
            if let localVideoTrack = webRTCManager.localVideoTrack {
                localVideoTrack.add(uiView)
                print("Native video observe added to view")
            } else {
                print("Native video observe is nil")
            }
        } else {
            if let remoteVideoTrack = webRTCManager.remoteVideoTrack {
                remoteVideoTrack.add(uiView)
                print("Distant video observe added to view")
            } else {
                print("Distant video observe is nil")
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject {
        var dad or mum: RTCVideoView

        init(_ dad or mum: RTCVideoView) {
            self.dad or mum = dad or mum
            tremendous.init()
            NotificationCenter.default.addObserver(self, selector: #selector(remoteVideoTrackAdded), title: .remoteVideoTrackAdded, object: nil)
        }

        @objc func remoteVideoTrackAdded() {
            DispatchQueue.foremost.async {
                self.dad or mum.webRTCManager.objectWillChange.ship()
            }
        }
    }
}

ContentView.swift

import SwiftUI
import WebRTC
import MultipeerConnectivity
import ReplayKit

struct ContentView: View {
    @StateObject non-public var webRTCManager = WebRTCManager()
    @State non-public var isBroadcasting = false
    @State non-public var isReceiving = false

    var physique: some View {
        VStack {
            if isBroadcasting {
                Textual content("Broadcasting")
                    .font(.headline)
                
                // Native video preview
                RTCVideoView(webRTCManager: webRTCManager, isLocal: true)
                    .body(peak: 200)
                    .background(Coloration.grey.opacity(0.3)) // Add a semi-transparent grey background
                    .cornerRadius(10)
                Button("Cease Broadcasting") {
                    webRTCManager.stopBroadcasting()
                    isBroadcasting = false
                }
                .padding()
                .background(Coloration.pink)
                .foregroundColor(.white)
                .cornerRadius(10)

                // ... (present code for SDP and ICE candidates)
            } else if isReceiving {
                Textual content("Receiving")
                    .font(.headline)
                RTCVideoView(webRTCManager: webRTCManager, isLocal: false)
                    .body(peak: 300)
                    .background(Coloration.grey.opacity(0.3)) // Add a semi-transparent grey background
                    .cornerRadius(10)
                Button("Cease Receiving") {
                    webRTCManager.stopReceiving()
                    isReceiving = false
                }
                .padding()
                .background(Coloration.pink)
                .foregroundColor(.white)
                .cornerRadius(10)
            } else {
                Button("Begin Broadcasting") {
                    webRTCManager.startBroadcasting()
                    isBroadcasting = true
                }
                .padding()
                .background(Coloration.blue)
                .foregroundColor(.white)
                .cornerRadius(10)

                Button("Begin Receiving") {
                    webRTCManager.startReceiving()
                    isReceiving = true
                }
                .padding()
                .background(Coloration.inexperienced)
                .foregroundColor(.white)
                .cornerRadius(10)
            }

            Textual content("Linked Friends: (webRTCManager.connectedPeers.rely)")
                .font(.headline)
                .padding()

            if !webRTCManager.connectedPeers.isEmpty {
                Textual content("Linked to:")
                ForEach(webRTCManager.connectedPeers, id: .self) { peer in
                    Textual content(peer.displayName)
                }
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Console of connecting system:

2024-07-08 22:04:22.574897+0100 ScreenShare[2745:914162] Steel API Validation Enabled
Distant video observe is nil
Distant video observe is nil
2024-07-08 22:04:56.058516+0100 ScreenShare[2745:914299] [Client] Updating selectors after delegate removing failed with: Error Area=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.commcenter.coretelephony.xpc was invalidated from this course of." UserInfo={NSDebugDescription=The connection to service named com.apple.commcenter.coretelephony.xpc was invalidated from this course of.}
2024-07-08 22:04:56.058657+0100 ScreenShare[2745:914299] [Client] Updating selectors after delegate addition failed with: Error Area=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.commcenter.coretelephony.xpc was invalidated from this course of." UserInfo={NSDebugDescription=The connection to service named com.apple.commcenter.coretelephony.xpc was invalidated from this course of.}
Peer connecting: iPhone
Distant video observe is nil
Peer linked: iPhone
Distant video observe is nil
Signaling state modified: 3
Stream added with ID: stream0
Video observe added: video0
Distant description set efficiently
Creating reply
Distant video observe set
Distant video observe added to view
Signaling state modified: 0
Native description (reply) set efficiently
SDP despatched to friends
ICE gathering state modified: 1
ICE candidate generated: candidate:617392483 1 udp 2122260223 192.168.1.112 63716 typ host era 0 ufrag umCW network-id 1 network-cost 10
ICE candidate despatched to friends
ICE candidate generated: candidate:3503244297 1 udp 2122194687 169.254.104.95 64683 typ host era 0 ufrag umCW network-id 2 network-cost 10
ICE candidate despatched to friends
Distant video observe added to view
Distant video observe added to view
ICE candidate generated: candidate:1783584147 1 tcp 1518280447 192.168.1.112 51096 typ host tcptype passive era 0 ufrag umCW network-id 1 network-cost 10
ICE candidate despatched to friends
ICE candidate generated: candidate:2655828217 1 tcp 1518214911 169.254.104.95 51097 typ host tcptype passive era 0 ufrag umCW network-id 2 network-cost 10
ICE candidate despatched to friends
Distant video observe added to view
ICE candidate generated: candidate:2776936407 1 udp 1686052607 2.28.217.67 63716 typ srflx raddr 192.168.1.112 rport 63716 era 0 ufrag umCW network-id 1 network-cost 10
ICE candidate despatched to friends
Distant video observe added to view
Distant video observe added to view
2024-07-08 22:05:06.151870+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [0].
2024-07-08 22:05:06.155864+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [1].
2024-07-08 22:05:06.158066+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [2].
2024-07-08 22:05:06.159428+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [3].
2024-07-08 22:05:06.160762+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [4].
2024-07-08 22:05:06.161831+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [5].
2024-07-08 22:05:06.162682+0100 ScreenShare[2745:914169] [GCKSession] Not in linked state, so giving up for participant [780785AF] on channel [6].
ICE gathering state modified: 2

I’ve fiddled with the initiatives settings and added something I’ve discovered might be inflicting the problem — as a result of its absence from the Information.plist — and added it in:

<plist model="1.0">
<dict>
    <key>NSBonjourServices</key>
    <array>
        <string>_screen-share._tcp</string>
        <string>_screen-share._udp</string>
    </array>
    <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
        <key>UISceneConfigurations</key>
        <dict/>
    </dict>
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
        <string>fetch</string>
        <string>nearby-interaction</string>
        <string>processing</string>
        <string>voip</string>
    </array>
</dict>
</plist>



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments