PageSourceSearch

https://app.buildcities.com/assets/EgressTemplatePage-fmRCWhD0.js

js buildcities.com collected 2026-09-28 07:31:47 UTC 8,652 bytes, 262 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-BoGG1wja.js";
2import { u as useLocation } from "./index-Cn8jK25t.js";
3import { L as LiveKitRoom, R as RoomAudioRenderer, u as useConnectionState, C as ConnectionState, a as useParticipants, b as useTracks, T as Track, V as VideoTrack } from "./vendor-livekit-__Gc1gfw.js";
4import { g as getParticipantName, a as getParticipantAvatarUrl, b as getParticipantInitials } from "./utils-J324L3oq.js";
5import "./vendor-shared-CkCIh-Sx.js";
6import "./vendor-search-Bv9bACL5.js";
7import "./vendor-ui-ClIxZ1gL.js";
8import "./vendor-realtime-LfCGF1rg.js";
9const isEgressParticipant = (p) => {
10  const identity = String(p.identity || "");
11  return identity.startsWith("EG_") || /egress/i.test(identity);
12};
13const STAGE_BG = "#0b0b0f";
14const AvatarTile = ({
15  participant
16}) => {
17  const name = getParticipantName(participant) || "Guest";
18  const avatarUrl = getParticipantAvatarUrl(participant.metadata);
19  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
20    display: "flex",
21    flexDirection: "column",
22    alignItems: "center",
23    justifyContent: "center",
24    gap: 16,
25    width: "100%",
26    height: "100%",
27    background: "linear-gradient(160deg, #16161d 0%, #0e0e13 100%)"
28  }
28, children: avatarUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx(
29    "img",
30    {
31      src: avatarUrl,
32      alt: "",
33      style: {
34        width: "22%",
35        aspectRatio: "1 / 1",
36        minWidth: 88,
37        maxWidth: 200,
38        borderRadius: "50%",
39        objectFit: "cover",
40        border: "3px solid rgba(255,255,255,0.14)"
41      }
42    }
43  ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
44    width: "22%",
45    aspectRatio: "1 / 1",
46    minWidth: 88,
47    maxWidth: 200,
48    borderRadius: "50%",
49    display: "flex",
50    alignItems: "center",
51    justifyContent: "center",
52    background: "rgba(255,255,255,0.09)",
53    color: "rgba(255,255,255,0.85)",
54    fontSize: "clamp(28px, 5vw, 64px)",
55    fontWeight: 600,
56    letterSpacing: 1
57  }, children: getParticipantInitials(name) }) });
58};
59const NamePlate = ({
60  name
61}) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
62  position: "absolute",
63  left: 14,
64  bottom: 12,
65  maxWidth: "calc(100% - 28px)",
66  padding: "5px 12px",
67  borderRadius: 999,
68  background: "rgba(0,0,0,0.55)",
69  color: "#fff",
70  fontSize: 15,
71  fontWeight: 600,
72  lineHeight: 1.2,
73  whiteSpace: "nowrap",
74  overflow: "hidden",
75  textOverflow: "ellipsis"
76}, children: name });
77const ParticipantTile = ({
78  participant,
79  cameraTrack
80}) => {
81  var _a;
82  const name = getParticipantName(participant) || "Guest";
83  const hasVideo = Boolean(cameraTrack && !((_a = cameraTrack.publication) == null ? void 0 : _a.isMuted));
84  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
85    position: "relative",
86    width: "100%",
87    height: "100%",
88    overflow: "hidden",
89    borderRadius: 12,
90    background: "#101015"
91  }, children: [
92    hasVideo && cameraTrack ? /* @__PURE__ */ jsxRuntimeExports.jsx(VideoTrack, { trackRef: cameraTrack, style: {
93      width: "100%",
94      height: "100%",
95      objectFit: "cover"
96    } }) : /* @__PURE__ */ jsxRuntimeExports.jsx(AvatarTile, { participant }),
97    /* @__PURE__ */ jsxRuntimeExports.jsx(NamePlate, { name })
98  ] });
99};
100const EgressStage = () => {
101  const participants = useParticipants();
102  const tracks = useTracks([Track.Source.Camera, Track.Source.ScreenShare], {
103    onlySubscribed: true
104  });
105  const humans = reactExports.useMemo(() => participants.filter((p) => !isEgressParticipant(p)), [participants]);
106  const screenShare = reactExports.useMemo(() => tracks.find((t) => t.source === Track.Source.ScreenShare), [tracks]);
107  const cameraByIdentity = reactExports.useMemo(() => {
108    var _a;
109    const map = /* @__PURE__ */ new Map();
110    for (const t of tracks) {
111      if (t.source === Track.Source.Camera && ((_a = t.participant) == null ? void 0 : _a.identity)) {
112        map.set(t.participant.identity, t);
113      }
114    }
115    return map;
116  }, [tracks]);
117  const columns = Math.max(1, Math.ceil(Math.sqrt(humans.length || 1)));
118  if (humans.length === 0) {
119    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
120      width: "100%",
121      height: "100%",
122      background: STAGE_BG
123    } });
124  }
125  if (screenShare) {
126    return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
127      display: "flex",
128      width: "100%",
129      height: "100%",
130      gap: 12,
131      padding: 12,
132      background: STAGE_BG,
133      boxSizing: "border-box"
134    }, children: [
135      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
136        flex: "1 1 auto",
137        minWidth: 0,
138        borderRadius: 12,
139        overflow: "hidden"
140      }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(VideoTrack, { trackRef: screenShare, style: {
141        width: "100%",
142        height: "100%",
143        objectFit: "contain",
144        background: "#000"
145      } }) }),
146      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
147        flex: "0 0 20%",
148        display: "flex",
149        flexDirection: "column",
150        gap: 12,
151        minWidth: 180
152      }, children: humans.slice(0, 4).map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
153        flex: "0 0 auto",
154        aspectRatio: "16 / 9"
155      }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ParticipantTile, { participant: p, cameraTrack: cameraByIdentity.get(p.identity) }) }, p.sid)) })
156    ] });
157  }
158  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
159    display: "grid",
160    gridTemplateColumns: `repeat(${columns}, 1fr)`,
161    gap: 12,
162    padding: 12,
163    width: "100%",
164    height: "100%",
165    background: STAGE_BG,
166    boxSizing: "border-box"
167  }, children: humans.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx(ParticipantTile, { participant: p, cameraTrack: cameraByIdentity.get(p.identity) }, p.sid)) });
168};
169const RecorderLifecycle = () => {
170  const connectionState = useConnectionState();
171  const startedRef = reactExports.useRef(false);
172  reactExports.useEffect(() => {
173    if (startedRef.current) return;
174    if (connectionState !== ConnectionState.Connected) return;
175    startedRef.current = true;
176    requestAnimationFrame(() => {
177      requestAnimationFrame(() => {
178        console.log("START_RECORDING");
179      });
180    });
181  }, [connectionState]);
182  reactExports.useEffect(() => {
183    if (!startedRef.current) return;
184    if (connectionState !== ConnectionState.Disconnected) return;
185    console.log("END_RECORDING");
186  }, [connectionState]);
187  return null;
188};
189const EgressTemplatePage = () => {
190  const {
191    search
192  } = useLocation();
193  const {
194    serverUrl,
195    token
196  } = reactExports.useMemo(() => {
197    const params = new URLSearchParams(search || "");
198    return {
199      serverUrl: params.get("url") || "",
200      token: params.get("token") || ""
201      // `layout` is also passed by the egress. This template renders one
202      // responsive layout that adapts to screen share and participant count, so
203      // there is nothing to branch on yet.
204    };
205  }, [search]);
206  const roomOptions = reactExports.useMemo(() => ({
207    adaptiveStream: false,
208    dynacast: false
209  }), []);
210  reactExports.useEffect(() => {
211    const {
212      body,
213      documentElement
214    } = document;
215    const previous = body.style.cssText;
216    body.style.cssText = "margin:0;padding:0;overflow:hidden;background:#0b0b0f;";
217    documentElement.style.background = "#0b0b0f";
218    return () => {
219      body.style.cssText = previous;
220    };
221  }, []);
222  if (!serverUrl || !token) {
223    return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
224      display: "flex",
225      alignItems: "center",
226      justifyContent: "center",
227      width: "100vw",
228      height: "100vh",
229      background: STAGE_BG,
230      color: "rgba(255,255,255,0.4)",
231      fontFamily: "system-ui, sans-serif",
232      fontSize: 14
233    }, children: "Recording template — expects ?url= and &token= from the egress service." });
234  }
235  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
236    width: "100vw",
237    height: "100vh",
238    background: STAGE_BG
239  }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
240    LiveKitRoom,
241    {
242      serverUrl,
243      token,
244      connect: true,
245      audio: false,
246      video: false,
247      options: roomOptions,
248      style: {
249        width: "100%",
250        height: "100%"
251      },
252      children: [
253        /* @__PURE__ */ jsxRuntimeExports.jsx(RoomAudioRenderer, {}),
254        /* @__PURE__ */ jsxRuntimeExports.jsx(RecorderLifecycle, {}),
255        /* @__PURE__ */ jsxRuntimeExports.jsx(EgressStage, {})
256      ]
257    }
258  ) });
259};
260export {
261  EgressTemplatePage as default
262};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.