Skip to content

AV foundation error for video relay from iPhone to iPad, can someone figure out what's wrong with the code? #3

@jk56wxftrt-sudo

Description

@jk56wxftrt-sudo

import AVFoundation
import MultipeerConnectivity
import Photos
import SwiftUI

// MARK: - 1. THE STREAM ENGINE (Multipeer & Camera)
class UniversalStreamManager: NSObject, ObservableObject, MCSessionDelegate, MCNearbyServiceAdvertiserDelegate, MCNearbyServiceBrowserDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {

@Published var connectedPeer: MCPeerID?
@Published var receivedImage: UIImage?
@Published var isStreaming = false

// Multipeer Properties
private let peerID = MCPeerID(displayName: UIDevice.current.name)
private let serviceType = "v-stream"
private var session: MCSession!
private var advertiser: MCNearbyServiceAdvertiser!
private var browser: MCNearbyServiceBrowser!

// Camera Properties
private let captureSession = AVCaptureSession()
private let videoDataOutput = AVCaptureVideoDataOutput()
private let context = CIContext()

override init() {
	super.init()
	session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
	session.delegate = self

	advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: serviceType)
	advertiser.delegate = self

	browser = MCNearbyServiceBrowser(peer: peerID, serviceType: serviceType)
	browser.delegate = self
}

// Role Selection
func startAsCamera() {
	setupCamera()
	advertiser.startAdvertisingPeer()
	isStreaming = true
}

func startAsMonitor() {
	browser.startBrowsingForPeers()
	isStreaming = true
}

// MARK: - Camera Capture Logic (iPhone 11+)
private func setupCamera() {
	captureSession.beginConfiguration()
	defer { captureSession.commitConfiguration() }

	guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
		let videoInput = try? AVCaptureDeviceInput(device: videoDevice)
	else {
		print("Failed to set up the video device.")
		return
	}

	if captureSession.canAddInput(videoInput) {
		captureSession.addInput(videoInput)
	}

	videoDataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
	if captureSession.canAddOutput(videoDataOutput) {
		captureSession.addOutput(videoDataOutput)
	}

	DispatchQueue.global(qos: .userInitiated).async {
		self.captureSession.startRunning()
	}
}

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
	guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
	let ciImage = CIImage(cvImageBuffer: imageBuffer)

	// Optimize: Downsample and compress for zero-latency peer-to-peer
	if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
		let uiImage = UIImage(cgImage: cgImage)
		if let data = uiImage.jpegData(compressionQuality: 0.2) {
			sendData(data)
		}
	}
}

private func sendData(_ data: Data) {
	if !session.connectedPeers.isEmpty {
		try? session.send(data, toPeers: session.connectedPeers, with: .unreliable)
	}
}

// MARK: - Receiver Logic (iPad 2020+)
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
	DispatchQueue.main.async {
		self.receivedImage = UIImage(data: data)
	}
}

// MARK: - Multipeer Delegates (Required)
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
	invitationHandler(true, session)
}

func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) {
	browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
}

func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
	DispatchQueue.main.async {
		self.connectedPeer = (state == .connected) ? peerID : nil
	}
}

// Unused MCSession Delegates
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {}
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, progress: Progress) {}
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {}
func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {}

}

// MARK: - 2. THE USER INTERFACE
struct ContentView: View {
@StateObject var manager = UniversalStreamManager()
@State private var isCameraMode = false

var body: some View {
	ZStack {
		Color.black.ignoresSafeArea()

		if !manager.isStreaming {
			VStack(spacing: 30) {
				Text("P2P Video Vault")
					.font(.system(size: 32, weight: .bold, design: .rounded))
					.foregroundColor(.white)

				Button(action: {
					isCameraMode = true
					manager.startAsCamera()
				}) {
					Label("iPhone: Start Camera", systemImage: "camera.fill")
						.frame(maxWidth: .infinity).padding().background(Color.blue).cornerRadius(15)
				}

				Button(action: {
					isCameraMode = false
					manager.startAsMonitor()
				}) {
					Label("iPad: Start Monitor", systemImage: "desktopcomputer")
						.frame(maxWidth: .infinity).padding().background(Color.green).cornerRadius(15)
				}
			}
			.foregroundColor(.white)
			.padding()
		} else {
			if isCameraMode {
				VStack {
					Text("Broadcasting to iPad...")
						.foregroundColor(.red)
						.font(.headline)
					ProgressView().tint(.white)
				}
			} else {
				MonitorView(manager: manager)
			}
		}
	}
}

}

struct MonitorView: View {
@ObservedObject var manager: UniversalStreamManager

var body: some View {
	VStack {
		if let image = manager.receivedImage {
			Image(uiImage: image)
				.resizable()
				.aspectRatio(contentMode: .fit)
				.cornerRadius(20)

			Button(action: { saveImage(image) }) {
				Label("Capture to Photos", systemImage: "arrow.down.circle.fill")
					.padding().background(Color.white).foregroundColor(.black).cornerRadius(12)
			}
			.padding()
		} else {
			Text("Waiting for iPhone Feed...")
				.foregroundColor(.gray)
		}
	}
}

func saveImage(_ image: UIImage) {
	PHPhotoLibrary.shared().performChanges({
		PHAssetChangeRequest.creationRequestForAsset(from: image)
	})
}

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions