oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hub
2
3import (
4 "context"
5 "encoding/json"
6)
7
8// SetMedia is §15.19: an admitted peer reports its own microphone and
9// camera state. The hub records it on the participation, where every
10// later roster — admission, resume, a kind change — carries it, and
11// fans a change out to every other admitted peer as
12// {type: "media", from, name, payload: MediaState}, the envelope
13// rebuilt from the sender's participation exactly as Fanout's is.
14//
15// §15.20 adds the receiving half to the same report — audioBlocked and
16// noSoundFrom, each present only when it has bad news, and 2026-09-28's
17// screenStalled beside them, and §15.24's hearing after those — and nothing
18// about the rules: it is still the sender's own claim, recorded and
19// carried in rosters, and fanned out only when it changes.
20//
21// It is state, not an event. A report that changes nothing is accepted
22// and sends nothing, so a client may repeat it freely — on every roster,
23// after every resume — and the room hears only changes. Nothing goes in
24// a replay ring: a peer that missed a `media` while detached learns the
25// current state from the roster its resume is answered with (§4.5),
26// which is the authority a ring would only have approximated.
27//
28// The sender's authority is Fanout's: a member re-derives standing
29// first (§15.12) and one whose standing has gone is ended here and
30// answered ErrNotYours; a guest never consults standing (ruling 14); a
31// participation not yet admitted is refused ErrNotAdmitted, because a
32// waiting guest has nobody to tell and learns nothing about the room by
33// asking (ruling 9).
34func (h *Hub) SetMedia(ctx context.Context, sqid, session string, from PeerID, m MediaState) error {
35 gone, err := h.memberStandingGone(ctx, sqid, session, from, true)
36 if err != nil {
37 return err
38 }
39 if gone {
40 return ErrNotYours
41 }
42 h.mu.Lock()
43 defer h.mu.Unlock()
44 r, ok := h.rooms[sqid]
45 if !ok {
46 return ErrUnknownPeer
47 }
48 p, found := h.findLocked(sqid, session, from)
49 if !found {
50 return ErrUnknownPeer
51 }
52 if !p.Admitted {
53 return ErrNotAdmitted
54 }
55 if p.Media != nil && p.Media.Equal(m) {
56 return nil
57 }
58 // A fresh value, never a write through the old pointer: rosters
59 // already sent hold that one. The list is copied too, so nothing the
60 // caller still holds is shared with a roster.
61 next := m
62 next.NoSoundFrom = append([]PeerID(nil), m.NoSoundFrom...)
63 next.ScreenStalled = append([]PeerID(nil), m.ScreenStalled...)
64 // §15.24's hearing keeps its emptiness: [] says "nobody yet", where
65 // nil says nothing at all.
66 if m.Hearing != nil {
67 next.Hearing = append(make([]PeerID, 0, len(m.Hearing)), m.Hearing...)
68 }
69 p.Media = &next
70 payload, err := json.Marshal(next)
71 if err != nil {
72 return err
73 }
74 out := Message{Type: TypeMedia, From: p.Peer, Name: p.Name, Payload: payload}
75 h.broadcastLocked(r, out, func(q *Participation) bool { return q.Admitted && q.Peer != p.Peer })
76 return nil
77}
78