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 os
3
4/// When "Reconnecting…" is said, and to whom (spec §15.23).
5///
6/// Paul, 2026-09-30: "During that call I also saw 'Reconnecting' appear
7/// occasionally over the video, but only for a second … I didn't notice any
8/// interruption in the call." The core puts the call in its `reconnecting`
9/// phase the moment its signalling stream ends, and the stream ends on
10/// purpose every few minutes (its renewal) and is resumed half a second
11/// later; the media never notices. So the words wait until a problem has
12/// lasted:
13///
14/// - the signalling stream down for `streamAfter` (4 s) — the whole call;
15/// - a connection `disconnected` or `failed` for `connectionAfter` (3 s),
16/// or no audio arriving from someone who is not muted for `audioAfter`
17/// (2 s) — that person's tile, "Reconnecting to Samuel…", unless it is
18/// everyone (two or more), which is the whole call again.
19///
20/// Once said, the words stay `holdAfter` (1 s) past the recovery, so they
21/// do not flicker. Pure: fed the facts as they are, with the time.
22struct ReconnectWatch: Equatable {
23 static let streamAfter = 4.0
24 static let connectionAfter = 3.0
25 static let audioAfter = 2.0
26 static let holdAfter = 1.0
27
28 /// One person's connection as it is now.
29 struct Peer: Equatable {
30 /// ICE `disconnected` or `failed`.
31 var connectionBad = false
32 /// No audio packets arrived at the last look, and they are not muted.
33 var audioMissing = false
34 }
35
36 private var streamDownSince: Double?
37 private var connectionBadSince: [String: Double] = [:]
38 private var audioMissingSince: [String: Double] = [:]
39 private var callClearSince: Double?
40 private var peerClearSince: [String: Double] = [:]
41
42 /// Whether the whole call is said to be reconnecting.
43 private(set) var call = false
44 /// The people whose tiles say "Reconnecting to …".
45 private(set) var peers: Set<String> = []
46
47 init() {}
48
49 /// One look. `streamDown` is the core's `reconnecting` phase; `people`
50 /// everyone in the call but you.
51 mutating func observe(at now: Double, streamDown: Bool, people: [String: Peer]) {
52 streamDownSince = streamDown ? (streamDownSince ?? now) : nil
53 for id in Array(connectionBadSince.keys) where people[id] == nil { connectionBadSince.removeValue(forKey: id) }
54 for id in Array(audioMissingSince.keys) where people[id] == nil { audioMissingSince.removeValue(forKey: id) }
55 var troubled: Set<String> = []
56 for (id, p) in people {
57 connectionBadSince[id] = p.connectionBad ? (connectionBadSince[id] ?? now) : nil
58 audioMissingSince[id] = p.audioMissing ? (audioMissingSince[id] ?? now) : nil
59 let connection = connectionBadSince[id].map { now - $0 >= Self.connectionAfter } ?? false
60 let audio = audioMissingSince[id].map { now - $0 >= Self.audioAfter } ?? false
61 if connection || audio { troubled.insert(id) }
62 }
63 let stream = streamDownSince.map { now - $0 >= Self.streamAfter } ?? false
64 let everyone = troubled.count >= 2 && troubled.count == people.count
65 let wantCall = stream || everyone
66 let wantPeers = wantCall ? [] : troubled
67
68 if wantCall {
69 call = true
70 callClearSince = nil
71 } else if call {
72 let since = callClearSince ?? now
73 callClearSince = since
74 if now - since >= Self.holdAfter {
75 call = false
76 callClearSince = nil
77 }
78 }
79
80 var shown = peers.filter { people[$0] != nil }
81 for id in wantPeers {
82 shown.insert(id)
83 peerClearSince.removeValue(forKey: id)
84 }
85 for id in shown where !wantPeers.contains(id) {
86 // Covered by the whole call's words, or better: held, then gone.
87 if wantCall {
88 shown.remove(id)
89 peerClearSince.removeValue(forKey: id)
90 continue
91 }
92 let since = peerClearSince[id] ?? now
93 peerClearSince[id] = since
94 if now - since >= Self.holdAfter {
95 shown.remove(id)
96 peerClearSince.removeValue(forKey: id)
97 }
98 }
99 for id in Array(peerClearSince.keys) where !shown.contains(id) { peerClearSince.removeValue(forKey: id) }
100 peers = shown
101 }
102}
103
104/// The words (spec §15.23).
105public enum ReconnectWords {
106 public static let call = "Reconnecting…"
107 public static func peer(_ name: String) -> String { "Reconnecting to \(name)\u{2026}" }
108}
109
110/// `log show --predicate 'subsystem == "net.amadan.oficina.calls" && category == "connection"' --info`,
111/// and the last lines in "Copy call diagnostics": when "Reconnecting…" was
112/// said and cleared.
113enum CallLog {
114 static let logger = Logger(subsystem: "net.amadan.oficina.calls", category: "connection")
115
116 static func info(_ message: String) {
117 logger.info("\(message, privacy: .public)")
118 AudioDiagnosticsStore.connection.log("info", message)
119 }
120}
121
122extension AudioDiagnosticsStore {
123 /// The connection log's lines, kept apart from the others.
124 public static let connection = AudioDiagnosticsStore()
125}
126