oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3// ---------------------------------------------------------------
4// Screen share, receiving (spec §5.4)
5// ---------------------------------------------------------------
6//
7// The web client is the contract here, and this file is its receiving
8// half ported rule for rule: a sharer adds its display as one `sendonly`
9// transceiver of its own, in a stream of its own, announces it with a
10// `share {on:true}` broadcast, and stops it by stopping the transceiver
11// and broadcasting `share {on:false}`. Nothing on the wire says which
12// m-line is the screen. Each receiver works it out, and a native app and
13// a browser looking at the same sender have to reach the same answer, or
14// one of them captions a face as a screen.
15
16/// What one arriving track is to the person looking at it.
17enum TrackRole: Equatable {
18 case audio, camera, screen
19}
20
21/// What a connection has recorded about the peer on the other end: the
22/// mid and stream its camera came in on, the stream everything else from
23/// it has arrived in, and whether it has said it is sharing. room.js's
24/// `entry` fields of the same names.
25struct ScreenClues: Equatable {
26 var cameraMid = ""
27 var cameraStream = ""
28 var peerStream = ""
29 var sharing = false
30}
31
32/// One arriving track, as the classifier needs it. `direction` is the
33/// transceiver's negotiated direction when there is one and the direction
34/// it was built with when there is not, spelled the way SDP spells it.
35struct ArrivingTrack: Equatable {
36 var kind: String
37 var streamId = ""
38 var mid = ""
39 var direction = ""
40}
41
42/// Tells a peer's screen from its camera. room.js's classifyTrack, in
43/// the same order and for the same reasons, which are set out there at
44/// length; the short form:
45///
46/// 1. A video track arriving `recvonly` on a connection that sends a
47/// camera is on an m-line the far side added, and the only one it adds
48/// is the screen.
49/// 2. The camera's own mid, once one is known.
50/// 3. The camera's stream id, for a sender that reports no mid.
51/// 4. The stream the peer's microphone came in: a sender puts its camera
52/// there and its screen somewhere else.
53///
54/// And when none of those can speak, the `share` message breaks the tie.
55/// It is last on purpose: it says the peer is showing a screen somewhere,
56/// not that this track is it.
57///
58/// `hasVideo` is whether this connection carries a camera of ours. The
59/// apps always add one, so rule 1 is the rule that decides in practice;
60/// the rest are here because a browser on the far side may not have one,
61/// and a port that dropped them would disagree with the browser the day
62/// that mattered.
63func classifyTrack(_ known: ScreenClues, _ track: ArrivingTrack, hasVideo: Bool) -> TrackRole {
64 if track.kind != "video" { return .audio }
65 if track.direction == "recvonly" && hasVideo { return .screen }
66 if !known.cameraMid.isEmpty && !track.mid.isEmpty {
67 return track.mid == known.cameraMid ? .camera : .screen
68 }
69 if !known.cameraStream.isEmpty && !track.streamId.isEmpty {
70 return track.streamId == known.cameraStream ? .camera : .screen
71 }
72 if !known.peerStream.isEmpty && !track.streamId.isEmpty {
73 return track.streamId == known.peerStream ? .camera : .screen
74 }
75 if known.cameraMid.isEmpty && known.cameraStream.isEmpty {
76 return known.sharing ? .screen : .camera
77 }
78 // A camera is known but there is nothing on this track to compare with
79 // it: a second video track is the screen.
80 return .screen
81}
82
83/// The words under a screen. The browser's, character for character —
84/// including the typographic apostrophe — so a person moving between the
85/// app and a browser tab reads the same caption on the same screen.
86public func screenCaption(_ name: String) -> String { name + "’s screen" }
87
88/// One thing the main stage can show: a person's camera or their screen.
89public enum StageItem: Hashable, Sendable {
90 case camera(String)
91 case screen(String)
92
93 public var peer: String {
94 switch self {
95 case .camera(let peer), .screen(let peer): return peer
96 }
97 }
98}
99
100/// What the main stage shows; Speaker view's rules are in SpeakerView.swift.
101public enum Stage {}
102