import CallsBroadcastKit import CoreMedia import ReplayKit /// The broadcast upload extension: ReplayKit's frames in, the app's socket /// out. It links CallsBroadcastKit and nothing else of Calls, and holds /// one frame buffer, because it lives under a memory ceiling of about /// fifty megabytes and is killed without a word past it. /// /// It never talks to the meeting. The app is already in the call and owns /// every peer connection; this only hands it pictures. final class SampleHandler: RPBroadcastSampleHandler { private var sender: BroadcastSender? override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) { guard let group = BroadcastPath.appGroup, let path = BroadcastPath.socket(appGroup: group) else { finish("Calls could not share your screen on this build.") return } let sender = BroadcastSender(path: path) // Two seconds: the countdown has just ended, and the app, which is in // the call, is listening already. Longer would only delay the answer // to someone who started a broadcast with no call to share it to. guard sender.connect(timeout: 2) else { finish("Join a call in Calls, then share your screen from there.") return } sender.onClosed = { [weak self] in self?.finish("You stopped sharing your screen in Calls.") } self.sender = sender } override func processSampleBuffer( _ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType ) { guard sampleBufferType == .video, let sender, let pixels = sampleBuffer.imageBuffer else { return } let orientation = (CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil) as? NSNumber)?.uint16Value ?? 1 let time = CMTimeGetSeconds(sampleBuffer.presentationTimeStamp) sender.send(pixels, orientation: orientation, timestampNs: UInt64(max(0, time) * 1_000_000_000)) } override func broadcastFinished() { sender?.close() sender = nil } /// Ends the broadcast with a sentence the system shows the person. The /// only way an extension can say anything at all. private func finish(_ sentence: String) { sender?.close() sender = nil finishBroadcastWithError( NSError(domain: "Calls", code: 1, userInfo: [NSLocalizedDescriptionKey: sentence])) } }