Live video on the AT Protocol

prelive: blue circles rather than green

+29 -13
+6 -2
js/app/components/live-dashboard/bento-grid.tsx
··· 3 3 borders, 4 4 Button, 5 5 Dashboard, 6 + useLivestream, 6 7 useLivestreamStore, 7 8 usePlayerStore, 8 9 useProfile, ··· 81 82 const ingestConnectionState = usePlayerStore((x) => x.ingestConnectionState); 82 83 const ingestStarted = usePlayerStore((x) => x.ingestStarted); 83 84 const emojiData = useEmojiData(); 85 + const livestream = useLivestream(); 84 86 85 87 // Calculate derived values 86 88 const isConnected = ingestConnectionState === "connected"; ··· 112 114 | "excellent" 113 115 | "good" 114 116 | "poor" 115 - | "offline" => { 117 + | "offline" 118 + | "pre-live" => { 116 119 if (!isLive) return "offline"; 120 + if (!livestream) return "pre-live"; 117 121 switch (segmentTiming.connectionQuality) { 118 122 case "good": 119 123 return "excellent"; ··· 124 128 default: 125 129 return "offline"; 126 130 } 127 - }, [isLive, segmentTiming.connectionQuality]); 131 + }, [isLive, livestream, segmentTiming.connectionQuality]); 128 132 129 133 // Calculate messages per minute 130 134 const messagesPerMinute = useMemo((): number => {
+2
js/app/components/live-dashboard/stream-monitor.tsx
··· 61 61 // Connection quality indicator 62 62 const getConnectionIcon = () => { 63 63 if (!isLive) return null; 64 + if (!ls) return <Wifi size={16} color="#3b82f6" />; 64 65 65 66 switch (segmentTiming.connectionQuality) { 66 67 case "good": ··· 76 77 77 78 const getConnectionColor = () => { 78 79 if (!isLive) return "red"; 80 + if (!ls) return "blue"; 79 81 80 82 switch (segmentTiming.connectionQuality) { 81 83 case "good":
+6 -2
js/components/src/components/dashboard/header.tsx
··· 36 36 } 37 37 38 38 interface StatusIndicatorProps { 39 - status: "excellent" | "good" | "poor" | "offline"; 39 + status: "excellent" | "good" | "poor" | "offline" | "pre-live"; 40 40 isLive: boolean; 41 41 } 42 42 ··· 44 44 const getStatusColor = () => { 45 45 if (!isLive) return bg.gray[500]; 46 46 switch (status) { 47 + case "pre-live": 48 + return bg.blue[500]; 47 49 case "excellent": 48 50 return bg.green[500]; 49 51 case "good": ··· 60 62 const getStatusText = () => { 61 63 if (!isLive) return "OFFLINE"; 62 64 switch (status) { 65 + case "pre-live": 66 + return "NOT LIVE"; 63 67 case "excellent": 64 68 return "EXCELLENT"; 65 69 case "good": ··· 101 105 uptime?: string; 102 106 bitrate?: string; 103 107 timeBetweenSegments?: number; 104 - connectionStatus?: "excellent" | "good" | "poor" | "offline"; 108 + connectionStatus?: "excellent" | "good" | "poor" | "offline" | "pre-live"; 105 109 problemsCount?: number; 106 110 onProblemsPress?: () => void; 107 111 }
+15 -9
js/components/src/components/dashboard/information-widget.tsx
··· 13 13 import { LayoutChangeEvent, Text, TouchableOpacity, View } from "react-native"; 14 14 import Svg, { Path, Line as SvgLine, Text as SvgText } from "react-native-svg"; 15 15 import { useAQState } from "../../hooks"; 16 - import { 17 - useLivestreamStore, 18 - useSegment, 19 - useViewers, 20 - } from "../../livestream-store"; 16 + import { useLivestream, useSegment, useViewers } from "../../livestream-store"; 21 17 import * as zero from "../../ui"; 22 18 import { InfoBox, InfoRow } from "../ui"; 23 19 ··· 50 46 const isCompactHeight = layoutMeasured && componentHeight < 350; 51 47 52 48 const seg = useSegment(); 53 - const livestream = useLivestreamStore((x) => x.livestream); 49 + const livestream = useLivestream(); 54 50 const viewers = useViewers(); 55 51 56 52 const getBitrate = useCallback((): number => { ··· 173 169 width: 8, 174 170 height: 8, 175 171 borderRadius: 4, 176 - backgroundColor: 177 - getConnectionStatus() === "good" 172 + backgroundColor: !livestream 173 + ? "#3b82f6" 174 + : getConnectionStatus() === "good" 178 175 ? "#22c55e" 179 176 : getConnectionStatus() === "warning" 180 177 ? "#f59e0b" ··· 182 179 }, 183 180 ]} 184 181 /> 182 + {!livestream && ( 183 + <Text style={[text.blue[400], { fontSize: 13, fontWeight: "600" }]}> 184 + (not live) 185 + </Text> 186 + )} 185 187 </View> 186 188 <TouchableOpacity 187 189 onPress={() => setShowViewers(!showViewers)} ··· 315 317 data={bitrateHistory} 316 318 width={componentWidth - 40} 317 319 height={120} 320 + color={livestream ? "#22c55e" : "#3b82f6"} 318 321 /> 319 322 </View> 320 323 )} ··· 395 398 data={bitrateHistory} 396 399 width={componentWidth - 40} 397 400 height={isCompactHeight ? 80 : 120} 401 + color={livestream ? "#22c55e" : "#3b82f6"} 398 402 /> 399 403 </View> 400 404 )} ··· 432 436 data, 433 437 width, 434 438 height, 439 + color = "#22c55e", 435 440 }: { 436 441 data: number[]; 437 442 width: number; 438 443 height: number; 444 + color?: string; 439 445 }) { 440 446 const maxDataValue = Math.max(...data, 1); 441 447 const minDataValue = Math.min(...data); ··· 515 521 </SvgText> 516 522 <Path 517 523 d={pathData} 518 - stroke="#22c55e" 524 + stroke={color} 519 525 strokeWidth="2" 520 526 fill="none" 521 527 strokeLinecap="round"