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