oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2import LiveKitWebRTC
3import os
4
5/// For the gate only (spec §15.22): a receiver whose screen decoder has
6/// wedged. Packets keep arriving, frames keep being handed to the decoder,
7/// and nothing comes out — which no keyframe mends, because it is this
8/// decoder instance that is stuck, and which a new transceiver does mend,
9/// because the far end's new stream gets a decoder of its own. That is the
10/// shape of a stall that "keyframes only" (0.4.3) left sitting at
11/// "recovering".
12///
13/// Off unless the interop executable turns it on before the call is
14/// built; the apps never do, and then the call's factory is WebRTC's own.
15@_spi(Fixtures) public enum DecoderWedge {
16 /// Wrap the call's decoders so they can be wedged. Read once, when the
17 /// call's factory is made.
18 nonisolated(unsafe) public static var enabled = false
19
20
21 private static let live = OSAllocatedUnfairLock(initialState: [WeakDecoder]())
22 private static let sticky = OSAllocatedUnfairLock(initialState: false)
23
24 static func register(_ decoder: WedgeableDecoder) {
25 live.withLock { list in
26 list.removeAll { $0.decoder == nil }
27 list.append(WeakDecoder(decoder: decoder))
28 }
29 }
30
31 /// Wedges the decoder now putting out the largest picture, which in the
32 /// gate is always the screen (its cameras are 320×240 to 640×480, and a
33 /// screen even at its smallest under a thin link is wider). `sticky`
34 /// wedges every decoder made afterwards too — only a screen's rebuild or
35 /// share again makes one mid-call — for a receiver no rebuild can mend,
36 /// the gate's "could not restore". Returns how many were wedged.
37 @discardableResult public static func wedgeScreens(sticky: Bool = false) -> Int {
38 self.sticky.withLock { $0 = sticky }
39 let decoders = live.withLock { $0.compactMap(\.decoder) }
40 guard let largest = decoders.map(\.area).max(), largest > 0 else { return 0 }
41 var count = 0
42 for d in decoders where d.area == largest {
43 d.wedge()
44 count += 1
45 }
46 return count
47 }
48
49 /// Every wedge lifted, and nothing wedged from now on.
50 public static func release() {
51 sticky.withLock { $0 = false }
52 for d in live.withLock({ $0.compactMap(\.decoder) }) { d.unwedge() }
53 }
54
55 static var isSticky: Bool { sticky.withLock { $0 } }
56
57 static func factory() -> RTCVideoDecoderFactory { WedgeableDecoderFactory() }
58}
59
60private struct WeakDecoder {
61 weak var decoder: WedgeableDecoder?
62}
63
64final class WedgeableDecoderFactory: NSObject, RTCVideoDecoderFactory {
65 private let inner = RTCDefaultVideoDecoderFactory()
66
67 func createDecoder(_ info: RTCVideoCodecInfo) -> RTCVideoDecoder? {
68 guard let decoder = inner.createDecoder(info) else { return nil }
69 // Only the VideoToolbox decoders are Objective-C all the way down.
70 // WebRTC's own VP8, VP9 and AV1 come back as wrapped native decoders,
71 // which the factory unwraps and never calls through this protocol, so
72 // they are handed over as they are and cannot be wedged.
73 guard ["H264", "H265"].contains(info.name.uppercased()) else { return decoder }
74 let wrapped = WedgeableDecoder(decoder)
75 if DecoderWedge.isSticky { wrapped.wedge() }
76 DecoderWedge.register(wrapped)
77 return wrapped
78 }
79
80 func supportedCodecs() -> [RTCVideoCodecInfo] { inner.supportedCodecs() }
81}
82
83final class WedgeableDecoder: NSObject, RTCVideoDecoder, @unchecked Sendable {
84 private let inner: RTCVideoDecoder
85 private struct State {
86 var wedged = false
87 var area = 0
88 }
89 private let state = OSAllocatedUnfairLock(initialState: State())
90
91 init(_ inner: RTCVideoDecoder) { self.inner = inner }
92
93 var area: Int { state.withLock { $0.area } }
94 func wedge() { state.withLock { $0.wedged = true } }
95 func unwedge() { state.withLock { $0.wedged = false } }
96
97 func setCallback(_ callback: @escaping RTCVideoDecoderCallback) {
98 inner.setCallback { [weak self] frame in
99 guard let self else { return }
100 let area = Int(frame.width) * Int(frame.height)
101 let wedged = self.state.withLock { s -> Bool in
102 s.area = area
103 return s.wedged
104 }
105 if !wedged { callback(frame) }
106 }
107 }
108
109 func startDecode(withNumberOfCores numberOfCores: Int32) -> Int {
110 inner.startDecode(withNumberOfCores: numberOfCores)
111 }
112
113 func release() -> Int { inner.release() }
114
115 func decode(
116 _ encodedImage: RTCEncodedImage, missingFrames: Bool, codecSpecificInfo info: RTCCodecSpecificInfo?,
117 renderTimeMs: Int64
118 ) -> Int {
119 inner.decode(encodedImage, missingFrames: missingFrames, codecSpecificInfo: info, renderTimeMs: renderTimeMs)
120 }
121
122 func implementationName() -> String { inner.implementationName() }
123}
124