Skip to content

Commit 9b6fd50

Browse files
committed
Merge branch 'livestream'
2 parents 3896c21 + ab7eeee commit 9b6fd50

14 files changed

Lines changed: 3220 additions & 0 deletions

livestream/OBS-SETUP.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# OBS Setup — WFR Streaming Overlay
2+
3+
This recipe captures your local overlay page in OBS and pushes it to YouTube or Twitch as a finished broadcast.
4+
5+
```
6+
[Car Pi] ─ WS :9080 ─┐
7+
8+
9+
[Laptop] localhost:8000/Streaming Overlay.html ← telemetry + video composite
10+
11+
12+
OBS (Browser Source, 1920×1080)
13+
14+
15+
rtmp://a.rtmp.youtube.com/live2/<key>
16+
rtmp://live.twitch.tv/app/<key>
17+
```
18+
19+
---
20+
21+
## 1. Serve the overlay page locally
22+
23+
From the project folder:
24+
25+
```bash
26+
python3 -m http.server 8000
27+
# or: npx serve .
28+
# or: npx http-server -p 8000
29+
```
30+
31+
Open `http://localhost:8000/Streaming%20Overlay.html` in a browser to sanity-check it's running.
32+
33+
---
34+
35+
## 2. Add the Browser Source in OBS
36+
37+
1. **Sources****+****Browser**
38+
2. Name it `WFR Overlay`
39+
3. **URL**: `http://localhost:8000/Streaming%20Overlay.html?variant=B&solo=1`
40+
- Change `variant=B` to `variant=A` if you want the broadcast-bar variant
41+
- `solo=1` hides the side-by-side comparison and shows one variant full-bleed
42+
4. **Width**: `1920`
43+
5. **Height**: `1080`
44+
6. **Custom FPS**: ✅ **60**
45+
7. **Custom CSS** (makes the page background transparent so your video shows through the gaps):
46+
```css
47+
html, body, #root { background: transparent !important; }
48+
```
49+
8. **Shutdown source when not visible**: ❌ (leave off — keeps telemetry alive)
50+
9. **Refresh browser when scene becomes active**: ✅
51+
52+
Click **OK**. The overlay should now render in OBS at full 1920×1080.
53+
54+
> **Want a pure overlay with no baked video?** Leave the MediaMTX host blank in the Settings panel. The page renders just the overlay chrome on transparent pixels, and you stack a separate OBS Media Source (or Video Capture Device) *underneath* the browser source. This is the recommended setup if your video source is already reliable via OBS directly.
55+
56+
---
57+
58+
## 3. Configure OBS output
59+
60+
**Settings → Output** (Advanced mode):
61+
62+
| Field | Value |
63+
|----------------|-------------------------------|
64+
| Encoder | NVENC HEVC / H.264 (or x264) |
65+
| Rate Control | CBR |
66+
| Bitrate | 6000–9000 kbps (YouTube 1080p60) · 6000 kbps max (Twitch) |
67+
| Keyframe | 2 s |
68+
| Preset | Quality / P5 |
69+
| Profile | high |
70+
71+
**Settings → Video**:
72+
73+
| Field | Value |
74+
|----------------------|------------|
75+
| Base (Canvas) Res | 1920×1080 |
76+
| Output (Scaled) Res | 1920×1080 |
77+
| FPS | 60 |
78+
79+
---
80+
81+
## 4. Stream key
82+
83+
**Settings → Stream**:
84+
85+
### YouTube
86+
1. Go to **YouTube Studio → Create → Go Live**.
87+
2. Copy the **Stream key**.
88+
3. OBS → **Settings → Stream** → Service: **YouTube - RTMPS** → paste key.
89+
90+
### Twitch
91+
1. Go to **dashboard.twitch.tv → Settings → Stream**.
92+
2. Copy your **Primary Stream Key**.
93+
3. OBS → **Settings → Stream** → Service: **Twitch** → paste key.
94+
95+
Click **Start Streaming**.
96+
97+
---
98+
99+
## 5. Public viewer page
100+
101+
Open `Public.html` and set your channel IDs at the top of the `<script>` block:
102+
103+
```js
104+
const CONFIG = {
105+
youtube: {
106+
channelId: 'UCxxxxxxxxxxxxxxxxxxxxxx', // your channel ID
107+
},
108+
twitch: {
109+
channel: 'wfrracing',
110+
parents: ['wfr.example.com'], // your deployed domain
111+
},
112+
};
113+
```
114+
115+
- **YouTube channel ID**[youtube.com/account_advanced](https://www.youtube.com/account_advanced)
116+
- **Twitch parents** → the Twitch player iframe requires a `parent=` param listing the domain(s) hosting the page. For local testing use `['localhost']`; for prod list every domain the page is served from.
117+
118+
Deploy `Public.html` anywhere — it's a single file with no build step. Netlify drop, GitHub Pages, S3, Cloudflare Pages, whatever you like.
119+
120+
---
121+
122+
## Troubleshooting
123+
124+
| Symptom | Fix |
125+
|---|---|
126+
| OBS browser source is black | Make sure `python3 -m http.server` is still running. Right-click source → **Refresh**. |
127+
| Overlay looks blurry | Check canvas + output res are both 1920×1080 and FPS is 60. |
128+
| Telemetry stuck on SIM | In the overlay's Settings panel (bottom-right), paste your WS URL and hit **Connect**. Close the panel before capture. |
129+
| Twitch embed shows "refused to connect" | Your domain isn't in `parents`. Add it to `CONFIG.twitch.parents`. |
130+
| YouTube embed shows "video unavailable" | You're not live yet, or the `channelId` is wrong. Once you Start Streaming, it auto-loads. |
131+
| Want to hide the settings panel before capture | Click **HIDE SETTINGS** in the top-right. The panel is OFF by default in `?solo=1` view. |
132+
133+
---
134+
135+
## File map
136+
137+
- `Streaming Overlay.html` — the local page OBS captures
138+
- `Public.html` — the lightweight page your audience visits
139+
- `OBS-SETUP.md` — this file

livestream/Streaming Overlay.html

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>WFR Stream · Telemetry Overlay</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com">
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&family=JetBrains+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
10+
<style>
11+
html, body, #root { margin: 0; padding: 0; background: #0a0a0b; color: #f4f4f5; }
12+
body { font-family: 'Inter', Helvetica, Arial, sans-serif; overflow: hidden; }
13+
* { box-sizing: border-box; }
14+
@keyframes wfrflash {
15+
from { opacity: 1; transform: translateY(0); }
16+
to { opacity: 0.4; transform: translateY(-2px); }
17+
}
18+
@keyframes wfrrecpulse {
19+
0%, 100% { opacity: 1; transform: scale(1); }
20+
50% { opacity: 0.35; transform: scale(0.8); }
21+
}
22+
input:focus { border-color: #D6AB39 !important; }
23+
button:hover { background: rgba(255,255,255,0.08) !important; }
24+
</style>
25+
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
26+
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
27+
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
28+
<!-- DBC-based CAN decoder (same pipeline as Pecan) -->
29+
<script src="src/candied.js"></script>
30+
</head>
31+
<body>
32+
<div id="root"></div>
33+
<script type="text/babel" src="src/decoder.jsx"></script>
34+
<script type="text/babel" src="src/telemetry-store.jsx"></script>
35+
<script type="text/babel" src="src/ws-service.jsx"></script>
36+
<script type="text/babel" src="src/widgets.jsx"></script>
37+
<script type="text/babel" src="src/overlays.jsx"></script>
38+
<script type="text/babel" src="src/video-stage.jsx"></script>
39+
<script type="text/babel" src="src/app.jsx"></script>
40+
</body>
41+
</html>

0 commit comments

Comments
 (0)