import LiveKitWebRTC import XCTest @testable import CallsKit /// The screen's sending half: what each "Optimise for" mode asks of the /// sender, the camera capped beside a share and given back after it, and /// the statistics line that says how the screen is actually leaving. /// room-share.test.mjs holds the browser's sharePlan and cameraCap to the /// same numbers; a change here belongs there too. final class ScreenSendTests: XCTestCase { // MARK: - Policy func testDetailKeepsResolutionAtTheHighestCeiling() { let plan = ScreenSendPolicy.screen(.detail, width: 1920, height: 1080) XCTAssertEqual(plan.degradation, .maintainResolution) XCTAssertEqual(plan.maxBitrate, 3_500_000) XCTAssertEqual(plan.maxFramerate, 30) XCTAssertNil(plan.scaleResolutionDownBy, "a 1080p screen is sent whole") XCTAssertTrue(plan.highPriority) XCTAssertEqual(plan.bitratePriority, 4) } func testMotionKeepsTheFrameRateAt1280() { let plan = ScreenSendPolicy.screen(.motion, width: 1920, height: 1080) XCTAssertEqual(plan.degradation, .maintainFramerate) XCTAssertEqual(plan.maxFramerate, 30) XCTAssertEqual(plan.scaleResolutionDownBy ?? 0, 1.5, accuracy: 0.0001, "1920 down to 1280") XCTAssertTrue(plan.highPriority) // A portrait window is scaled by its long side, too — and then a // little further, to H.264 level 3.1's 3,600 blocks (see // realtimeMacroblocks): 800×1280 would be 4,000. let portrait = ScreenSendPolicy.screen(.motion, width: 1000, height: 1600).scaleResolutionDownBy ?? 0 XCTAssertGreaterThan(portrait, 1.25) XCTAssertLessThanOrEqual(blocks(1000, 1600, portrait), 3600) // Something already small enough is left alone. XCTAssertNil(ScreenSendPolicy.screen(.motion, width: 1280, height: 720).scaleResolutionDownBy) } /// Paul's freeze of 2026-09-28: a 16:10 MacBook display in motion mode /// was 1280×830 at its cap, over level 3.1's 3,600 blocks, and the Mac's /// encoder refused every frame at that size. Motion now fits the level; /// a screencast source (every other mode) is not held to it. func testMotionFitsH264Level31() { for (w, h) in [(3024, 1964), (1920, 1246), (2560, 1600), (1920, 1200), (1470, 956), (1920, 1080)] { let scale = ScreenSendPolicy.screen(.motion, width: w, height: h).scaleResolutionDownBy ?? 1 XCTAssertLessThanOrEqual(blocks(w, h, scale), 3600, "\(w)×\(h) at 1/\(scale)") XCTAssertGreaterThan(blocks(w, h, scale), 3200, "\(w)×\(h) is not shrunk further than it has to be") } // 1080p at 1280×720 is exactly the level, and unchanged. XCTAssertEqual(ScreenSendPolicy.screen(.motion, width: 1920, height: 1080).scaleResolutionDownBy ?? 0, 1.5, accuracy: 0.0001) // Balanced and detail are screencasts and keep their full size. XCTAssertNil(ScreenSendPolicy.screen(.balanced, width: 1920, height: 1246).scaleResolutionDownBy) XCTAssertNil(ScreenSendPolicy.screen(.detail, width: 1920, height: 1200).scaleResolutionDownBy) // A share that started in motion keeps a camera-like source whatever // the mode becomes, and stays within the level. let switched = ScreenSendPolicy.screen(.detail, width: 1920, height: 1246, screencast: false) XCTAssertLessThanOrEqual(blocks(1920, 1246, switched.scaleResolutionDownBy ?? 1), 3600) } private func blocks(_ w: Int, _ h: Int, _ scale: Double) -> Int { ((Int(Double(w) / scale) + 15) / 16) * ((Int(Double(h) / scale) + 15) / 16) } func testBalancedIsTheDefaultAndKeepsTheFrameRateAtFullSize() { XCTAssertEqual(ScreenShareMode.allCases.first, .balanced) let plan = ScreenSendPolicy.screen(.balanced, width: 1920, height: 1080) // Frames are held; resolution gives way only when it must, from full // size rather than motion's 1280. XCTAssertEqual(plan.degradation, .maintainFramerate) XCTAssertNil(plan.scaleResolutionDownBy) XCTAssertEqual(plan.maxBitrate, 3_000_000) XCTAssertEqual(plan.maxFramerate, 30) XCTAssertTrue(plan.highPriority) XCTAssertTrue(ScreenSendPolicy.screencast(.balanced)) XCTAssertTrue(ScreenSendPolicy.screencast(.detail)) XCTAssertFalse(ScreenSendPolicy.screencast(.motion)) } func testH264IsPreferredAndNothingIsDropped() { let factory = RTCPeerConnectionFactory( encoderFactory: RTCDefaultVideoEncoderFactory(), decoderFactory: RTCDefaultVideoDecoderFactory()) let codecs = factory.rtpSenderCapabilities(forKind: "video").codecs let preferred = ScreenSendPolicy.preferringH264(codecs) XCTAssertEqual(preferred.count, codecs.count, "every codec stays on offer") XCTAssertEqual(preferred.first?.name, "H264") XCTAssertTrue(preferred.contains { $0.name == "VP8" }, "a browser without H.264 still has VP8") } func testAScreenLargerThan1920IsNeverSentLarger() { for mode in ScreenShareMode.allCases { let plan = ScreenSendPolicy.screen(mode, width: 3840, height: 2160) XCTAssertGreaterThanOrEqual(plan.scaleResolutionDownBy ?? 1, 2, "\(mode)") } } func testAnUnknownSizeLeavesTheScaleAlone() { for mode in ScreenShareMode.allCases { XCTAssertNil(ScreenSendPolicy.screen(mode).scaleResolutionDownBy, "\(mode)") } } func testTheCameraIsCappedWhileSharingAndGivenBackAfter() { let capped = ScreenSendPolicy.camera(sharing: true, height: 480) XCTAssertEqual(capped.maxBitrate, 500_000) XCTAssertEqual(capped.scaleResolutionDownBy ?? 0, 480.0 / 360.0, accuracy: 0.0001) XCTAssertFalse(capped.highPriority) XCTAssertNil(ScreenSendPolicy.camera(sharing: true, height: 360).scaleResolutionDownBy) XCTAssertEqual(ScreenSendPolicy.camera(sharing: false), EncodingPlan(), "the encoder's own defaults") } func testTheModeIsRemembered() { let defaults = UserDefaults.standard let saved = defaults.object(forKey: ScreenShareMode.key) defer { defaults.set(saved, forKey: ScreenShareMode.key) } defaults.removeObject(forKey: ScreenShareMode.key) XCTAssertEqual(ScreenShareMode.remembered, .balanced) ScreenShareMode.remembered = .motion XCTAssertEqual(ScreenShareMode.remembered, .motion) defaults.set("nonsense", forKey: ScreenShareMode.key) XCTAssertEqual(ScreenShareMode.remembered, .balanced) } // MARK: - Applying it to a real sender /// The plan reaches a real WebRTC sender, and the camera's defaults /// really come back: set, read back, restored. No call is placed. func testAPlanIsAppliedToASenderAndRestored() throws { let factory = RTCPeerConnectionFactory() let configuration = RTCConfiguration() configuration.sdpSemantics = .unifiedPlan let pc = try XCTUnwrap( factory.peerConnection( with: configuration, constraints: RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil), delegate: nil)) defer { pc.close() } let track = factory.videoTrack(with: factory.videoSource(), trackId: "camera") let sender = try XCTUnwrap(pc.add(track, streamIds: ["calls"])) XCTAssertTrue(sender.apply(ScreenSendPolicy.camera(sharing: true, height: 480))) var encoding = try XCTUnwrap(sender.parameters.encodings.first) XCTAssertEqual(encoding.maxBitrateBps?.intValue, 500_000) XCTAssertEqual(encoding.scaleResolutionDownBy?.doubleValue ?? 0, 480.0 / 360.0, accuracy: 0.001) XCTAssertFalse( sender.apply(ScreenSendPolicy.camera(sharing: true, height: 480)), "the same plan twice is a no-op") XCTAssertTrue(sender.apply(ScreenSendPolicy.camera(sharing: false))) encoding = try XCTUnwrap(sender.parameters.encodings.first) XCTAssertNil(encoding.maxBitrateBps) XCTAssertEqual(encoding.scaleResolutionDownBy?.doubleValue ?? 1, 1, accuracy: 0.001) XCTAssertTrue(sender.apply(ScreenSendPolicy.screen(.motion, width: 1920, height: 1080))) let parameters = sender.parameters encoding = try XCTUnwrap(parameters.encodings.first) XCTAssertEqual(encoding.maxBitrateBps?.intValue, 2_500_000) XCTAssertEqual(encoding.maxFramerate?.intValue, 30) XCTAssertEqual(encoding.networkPriority, .high) XCTAssertEqual(encoding.bitratePriority, 4) XCTAssertEqual( parameters.degradationPreference?.intValue, RTCDegradationPreference.maintainFramerate.rawValue) } // MARK: - The readout private typealias Stats = [String: (type: String, values: [String: Any])] private func stats(mid: String = "2", local: String = "host", remote: String = "host") -> Stats { [ "OT1": ( "outbound-rtp", [ "mid": mid, "frameWidth": 1920, "frameHeight": 1080, "framesPerSecond": 8.0, "framesSent": 400, "framesEncoded": 400, "qualityLimitationReason": "bandwidth", "qualityLimitationDurations": ["none": 2.0, "bandwidth": 30.0, "cpu": 0.0, "other": 0.0], "targetBitrate": 1_200_000, "bytesSent": 5_000_000, "encoderImplementation": "VideoToolbox", "totalEncodeTime": 8.0, "mediaSourceId": "SV1", "codecId": "C1", ] ), "OT0": ("outbound-rtp", ["mid": "1", "frameWidth": 640, "framesPerSecond": 30.0]), "SV1": ("media-source", ["frames": 900, "framesPerSecond": 30.0, "width": 1920, "height": 1080]), "C1": ("codec", ["mimeType": "video/H264"]), "RI1": ("remote-inbound-rtp", ["localId": "OT1", "packetsLost": 12, "roundTripTime": 0.085]), "T1": ("transport", ["selectedCandidatePairId": "P1"]), "P1": ("candidate-pair", ["localCandidateId": "L1", "remoteCandidateId": "R1"]), "L1": ("local-candidate", ["candidateType": local]), "R1": ("remote-candidate", ["candidateType": remote]), ] } func testTheScreensOwnStreamIsReadByMid() throws { let r = try XCTUnwrap(ScreenSendReport.read(stats(), mid: "2")) XCTAssertEqual(r.width, 1920) XCTAssertEqual(r.height, 1080) XCTAssertEqual(r.framesPerSecond, 8) XCTAssertEqual(r.capturedPerSecond, 30, "the capture is fine; the sender is not keeping up") XCTAssertEqual(r.framesCaptured, 900) XCTAssertEqual(r.sourceWidth, 1920) XCTAssertEqual(r.limitation, "bandwidth") XCTAssertEqual(r.limitationSeconds["bandwidth"], 30) XCTAssertEqual(r.targetBitrate, 1_200_000) XCTAssertEqual(r.codec, "video/H264") XCTAssertTrue(r.hardwareEncoder) XCTAssertEqual(r.encodeMsPerFrame, 20, accuracy: 0.001) XCTAssertEqual(r.packetsLost, 12) XCTAssertEqual(r.roundTripTime, 0.085) XCTAssertEqual(r.path, "host") XCTAssertNil(ScreenSendReport.read(stats(), mid: "9"), "a stream not negotiated yet") XCTAssertNil(ScreenSendReport.read(stats(), mid: "")) } func testARelayAtEitherEndIsTheRelay() { XCTAssertEqual(ScreenSendReport.path(stats(local: "relay")), "relay") XCTAssertEqual(ScreenSendReport.path(stats(remote: "relay")), "relay") XCTAssertEqual(ScreenSendReport.path(stats(local: "srflx", remote: "host")), "srflx") } func testTheSummaryIsOneLine() throws { var r = try XCTUnwrap(ScreenSendReport.read(stats(local: "relay"), mid: "2")) XCTAssertEqual( r.summary, "Screen: 1920×1080 at 8 fps, limited by bandwidth, 1.2 Mbit/s, relay (H264 hardware)") var previous = r previous.bytesSent = 4_900_000 previous.timestamp = 9 r.timestamp = 10 r = r.measured(since: previous) XCTAssertEqual(r.sentBitrate, 800_000, "the rate actually sent wins over the target") XCTAssertTrue(r.summary.contains("800 kbit/s")) XCTAssertEqual(ScreenSendReport().summary, "Screen: starting…") } func testTheSlowestConnectionIsTheOneShown() { var fast = ScreenSendReport() fast.framesPerSecond = 29 var slow = ScreenSendReport() slow.framesPerSecond = 4 XCTAssertEqual(ScreenSendReport.worst([fast, slow])?.framesPerSecond, 4) XCTAssertNil(ScreenSendReport.worst([])) } }