tangled
alpha
login
or
join now
stream.place
/
streamplace
74
fork
atom
Live video on the AT Protocol
74
fork
atom
overview
issues
1
pulls
pipelines
prelive: blue circles rather than green
Eli Mallon
3 weeks ago
5f07a1df
3d308438
+29
-13
4 changed files
expand all
collapse all
unified
split
js
app
components
live-dashboard
bento-grid.tsx
stream-monitor.tsx
components
src
components
dashboard
header.tsx
information-widget.tsx
+6
-2
js/app/components/live-dashboard/bento-grid.tsx
···
3
borders,
4
Button,
5
Dashboard,
0
6
useLivestreamStore,
7
usePlayerStore,
8
useProfile,
···
81
const ingestConnectionState = usePlayerStore((x) => x.ingestConnectionState);
82
const ingestStarted = usePlayerStore((x) => x.ingestStarted);
83
const emojiData = useEmojiData();
0
84
85
// Calculate derived values
86
const isConnected = ingestConnectionState === "connected";
···
112
| "excellent"
113
| "good"
114
| "poor"
115
-
| "offline" => {
0
116
if (!isLive) return "offline";
0
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;
0
64
65
switch (segmentTiming.connectionQuality) {
66
case "good":
···
76
77
const getConnectionColor = () => {
78
if (!isLive) return "red";
0
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) {
0
0
47
case "excellent":
48
return bg.green[500];
49
case "good":
···
60
const getStatusText = () => {
61
if (!isLive) return "OFFLINE";
62
switch (status) {
0
0
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"
0
178
? "#22c55e"
179
: getConnectionStatus() === "warning"
180
? "#f59e0b"
···
182
},
183
]}
184
/>
0
0
0
0
0
185
</View>
186
<TouchableOpacity
187
onPress={() => setShowViewers(!showViewers)}
···
315
data={bitrateHistory}
316
width={componentWidth - 40}
317
height={120}
0
318
/>
319
</View>
320
)}
···
395
data={bitrateHistory}
396
width={componentWidth - 40}
397
height={isCompactHeight ? 80 : 120}
0
398
/>
399
</View>
400
)}
···
432
data,
433
width,
434
height,
0
435
}: {
436
data: number[];
437
width: number;
438
height: number;
0
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";
0
0
0
0
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"