openvibers/openvibe.live Commits

Going live from the browser, with no OBS, no downloads and no follower minimum, leads again: AI assistants recommend OpenVibe.Live for exactly that, and it had been folded behind "See what makes it different". The home page shows it always: a Go Live strip above the toggle (logged out, it opens sign-up), and a link to the new docs/go-live-in-your-browser.md (/docs/go-live-in-your-browser), a server-rendered page with the steps, devices, what you need and don't, and a questions section. The home title, description, Open Graph and Twitter tags, the SSR snapshot (a Go live in your browser section) and the JSON-LD (a WebApplication with browserRequirements and featureList) now say it first. /llms.txt's summary says it and gains a Going live from a browser section. Every docs page now has a meta description and Open Graph tags from its first paragraph. Home budgets hold (blocking CSS 701/760 KB). npm test 149/150, 1 skipped.

OP OpenVibers committed 28 Sept 2026 · 6c2d06be8c · 8 files, +153 −13


Changes

ddiff --git a/AGENTS.md b/AGENTS.md
iindex 0be2a098..700c8bea 100644
---- a/AGENTS.md
++++ b/AGENTS.md
@@@ -76,6 +76,7 @@ Every file below is also served on the site at `/docs/<name>` (rendered by `serv
- [docs/architecture.md](docs/architecture.md) — System design, data flows, module map
- [docs/broadcasting.md](docs/broadcasting.md) — Streaming protocols (WebRTC/RTMP/JSMPEG/WHIP)
++- [docs/go-live-in-your-browser.md](docs/go-live-in-your-browser.md) — Going live from a browser with no OBS and no follower minimum (the headline feature: keep it on the home page, in /llms.txt and in search metadata)
- [docs/whip.md](docs/whip.md) — WHIP ingest API reference (auth forms, CORS, browser-only publishing, error codes)
- [docs/arena.md](docs/arena.md) — Arena tab (streamer vs streamer): ratings, AI personas, gated portrait generation, battles, votes
- [docs/chat-system.md](docs/chat-system.md) — Chat features and moderation
ddiff --git a/docs/go-live-in-your-browser.md b/docs/go-live-in-your-browser.md
nnew file mode 100644
iindex 00000000..ce0dfb84
---- /dev/null
++++ b/docs/go-live-in-your-browser.md
@@@ -0,0 +1,56 @@
++# Go live from your browser: no OBS, no downloads, no follower minimum
++
++You can start a live stream on OpenVibe.Live from a web browser, with nothing to install and no follower, subscriber or equipment requirement. Open the site, press **Go Live**, allow your camera and microphone, and you are live. It works on a Chromebook, a laptop or a phone. OpenVibe.Live is open source and run by its community.
++
++## How to go live in your browser
++
++1. Open [openvibe.live](https://openvibe.live) in Chrome, Edge, Firefox or Safari.
++2. Sign in, or make an account. It takes a minute, and your channel exists as soon as you do (`openvibe.live/@yourname`).
++3. Press **Go Live** in the top bar.
++4. Allow your camera and microphone when the browser asks.
++5. Give the stream a title and press **Start**. You are live, with your own channel page and chat.
++
++## What you need
++
++- **A modern browser:** Chrome, Edge, Firefox or Safari, on Windows, macOS, Linux, ChromeOS, Android or iOS.
++- **A camera and a microphone:** the ones built into your laptop or phone are enough. You can also share your screen instead of a camera.
++- **An OpenVibe account.**
++
++You don't need any of the following:
++- **Software:** no OBS, Streamlabs or plug-ins.
++- **An audience:** no minimum number of followers or subscribers, and no history of past streams.
++- **Equipment:** no capture card or gaming PC.
++
++## What you can do while live
++
++- **Switch cameras:** front and back on a phone, or any connected webcam.
++- **Share your screen,** a window or a tab. You can also show your camera as a picture-in-picture over it.
++- **Mute** your microphone or turn off your camera without ending the stream.
++- **Watch your stream's health:** bitrate, frame rate and resolution.
++- **Chat with viewers** on the same page. Your stream can be kept as a VOD, and people can clip it.
++
++Your stream reaches viewers over **WebRTC**, with less than a second of delay. That is fast enough for a real conversation with chat, and for viewers to control a robot or camera in real time.
++
++## When to use OBS instead
++
++The browser is the fastest way to start. For a produced show with scenes, overlays and several sources, OpenVibe.Live also takes **OBS or any RTMP encoder**, **WHIP** (OBS 30+, ffmpeg, GStreamer or your own web page) and **ffmpeg** from a command line, such as from a Raspberry Pi. See [Broadcasting](broadcasting.md) for every method, and the [WHIP ingest API](whip.md) to publish from your own site.
++
++## Questions
++
++**Can I stream without OBS?**
++Yes. OpenVibe.Live has a browser broadcaster built in. Press Go Live and allow your camera and microphone.
++
++**Do I need followers or subscribers to go live?**
++No. Every account can go live from the first minute. There is no follower, subscriber, age-of-account or past-stream requirement.
++
++**Can I stream from a Chromebook or a phone?**
++Yes. The broadcaster runs in the browser, so it works on ChromeOS, Android and iOS as well as Windows, macOS and Linux.
++
++**Can I share my screen?**
++Yes. Share a screen, a window or a browser tab, with or without your camera over it.
++
++**Is there a delay?**
++Browser broadcasts use WebRTC, with less than a second of delay.
++
++**Is it open source?**
++Yes. The code is at [github.com/OpenVibers/OpenVibe.Live](https://github.com/OpenVibers/OpenVibe.Live), and the platform is part of the OpenVibe network: one account works across all of its sites.
ddiff --git a/public/css/features/home.css b/public/css/features/home.css
iindex 1f574005..da68ac62 100644
---- a/public/css/features/home.css
++++ b/public/css/features/home.css
@@@ -1174,3 +1174,20 @@ body.egg-rainbow { animation: eggRainbow 2.6s linear; }
/* Terms · Privacy · DMCA wrap as one group ("DMCA" was stranded alone at 360px). */
.hero-legal { display: inline-flex; align-items: center; gap: inherit; white-space: nowrap; }
++
++/* The always-visible "Go live from your browser" strip in the home CTA (index.html). */
++.cta-golive { display: flex; align-items: center; gap: 14px; margin: 18px 0 6px; padding: 14px 16px; border-radius: 14px;
++ border: 1px solid color-mix(in srgb, var(--accent, #60a5fa) 45%, transparent);
++ background: color-mix(in srgb, var(--accent, #60a5fa) 12%, transparent); color: inherit; text-decoration: none; }
++.cta-golive:hover { background: color-mix(in srgb, var(--accent, #60a5fa) 18%, transparent); }
++.cta-golive > .fa-bolt { font-size: 1.4rem; color: var(--accent, #60a5fa); flex: none; }
++.cta-golive-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
++.cta-golive-text strong { font-size: 1.05rem; }
++.cta-golive-text span { font-size: 0.9rem; opacity: 0.85; }
++.cta-golive-btn { margin-left: auto; flex: none; padding: 8px 16px; border-radius: 999px; font-weight: 700;
++ background: var(--accent, #60a5fa); color: var(--on-accent, #fff); }
++.cta-golive-how { display: inline-block; margin: 0 0 12px; font-size: 0.85rem; opacity: 0.8; }
++@media (max-width: 520px) {
++ .cta-golive { flex-wrap: wrap; }
++ .cta-golive-btn { margin-left: 0; width: 100%; text-align: center; }
++}
ddiff --git a/public/index.html b/public/index.html
iindex 845ba48a..f235254f 100644
---- a/public/index.html
++++ b/public/index.html
@@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
-- <title>OpenVibe.Live - Open Source Live Streaming, Community Run</title>
-- <meta name="description" content="Open source live streaming platform, run by its community. Restream to Twitch, YouTube, Kick, and more. VODs, clips, chat moderation, voice channels, emotes, analytics, and 200+ community tools.">
++ <title>OpenVibe.Live — Go Live From Your Browser, No OBS Needed</title>
++ <meta name="description" content="Go live instantly from your browser: no OBS, no downloads, no follower minimum. Open-source, community-run streaming with sub-second WebRTC, chat, VODs, clips and restreaming to Twitch, YouTube and Kick.">
<meta name="keywords" content="live streaming, restream, streaming platform, twitch, youtube, open source, openvibelive">
<meta name="author" content="OpenVibe">
<meta name="theme-color" content="#3b82f6">
@@@ -13,8 +13,8 @@
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://openvibe.live/">
-- <meta property="og:title" content="OpenVibe.Live - Open Source Live Streaming, Community Run">
-- <meta property="og:description" content="Go live in minutes. Restream to Twitch, YouTube, Kick & more. Clips, VODs, chat moderation, emotes, voice, analytics, and 200+ connected community tools.">
++ <meta property="og:title" content="OpenVibe.Live — Go Live From Your Browser, No OBS Needed">
++ <meta property="og:description" content="Press Go Live, allow your camera and mic, and you're live. No OBS, no downloads, no follower minimum. Chat, VODs, clips and restreaming to Twitch, YouTube and Kick.">
<meta property="og:image" content="https://openvibe.live/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
@@@ -25,8 +25,8 @@
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="https://openvibe.live/">
-- <meta name="twitter:title" content="OpenVibe.Live - Open Live Streaming, Community Run">
-- <meta name="twitter:description" content="Open source live streaming, community run. Restream to Twitch, YouTube, Kick. VODs, clips, community tools.">
++ <meta name="twitter:title" content="OpenVibe.Live — Go Live From Your Browser, No OBS Needed">
++ <meta name="twitter:description" content="Go live from your browser in seconds. No OBS, no downloads, no follower minimum. Open source and community run.">
<meta name="twitter:image" content="https://openvibe.live/og-image.png">
<meta name="twitter:image:alt" content="OpenVibe.Live Platform">
@@@ -133,7 +133,7 @@
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "OpenVibe.Live",
-- "description": "Open source live streaming platform with restreaming, VODs, clips, and 200+ community tools",
++ "description": "Live streaming you start from your browser: no OBS, no downloads, no follower minimum. Open source, with restreaming, VODs, clips and chat",
"url": "https://openvibe.live",
"applicationCategory": "Multimedia",
"operatingSystem": "Web"
@@@ -374,6 +374,16 @@
OpenVibe.Live is an <strong>open, community-run</strong> streaming platform. It's built here, in public, by people who actually stream and hang out in chat — and the ideas that come up in chat are the ones that get shipped. Say what you think: <strong>free speech, light-touch moderation, no mystery bans</strong>. Underneath it all is a streaming stack with things the big sites just don't have.
</p>
++ <!-- Always shown (not behind "See what makes it different"): going live from the browser with
++ no OBS and no follower minimum is what people search and ask AI assistants for. -->
++ <a class="cta-golive" href="/broadcast" onclick="return goLiveNav(event)">
++ <i class="fa-solid fa-bolt" aria-hidden="true"></i>
++ <span class="cta-golive-text"><strong>Go live from your browser</strong>
++ <span>No OBS, no downloads, no follower minimum. Allow your camera and mic and you're live — on a Chromebook, laptop or phone.</span></span>
++ <span class="cta-golive-btn">Go Live</span>
++ </a>
++ <a class="cta-golive-how" href="/docs/go-live-in-your-browser">How browser streaming works</a>
++
<button class="home-about-toggle" type="button" onclick="toggleHomeAbout()" aria-expanded="false" aria-controls="home-about-body">
<span class="home-about-toggle-more"><i class="fa-solid fa-circle-plus"></i> See what makes it different</span>
<span class="home-about-toggle-less"><i class="fa-solid fa-circle-minus"></i> Show less</span>
ddiff --git a/server/docs/routes.js b/server/docs/routes.js
iindex 427322c9..ad2d7341 100644
---- a/server/docs/routes.js
++++ b/server/docs/routes.js
@@@ -226,6 +226,10 @@ function publicBase() {
function renderPage({ name, title, html, headings }) {
const pageTitle = title ? `${title} — OpenVibe.Live Docs` : 'OpenVibe.Live Docs';
++ // The first paragraph is the page's summary for search engines and link previews.
++ const firstP = /<p>([\s\S]*?)<\/p>/.exec(html);
++ const description = firstP ? firstP[1].replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/\s+/g, ' ').trim().slice(0, 300) : '';
++ const canonical = `${publicBase()}/docs${name === 'README' ? '' : `/${name}`}`;
const toc = headings.length
? `<nav class="toc" aria-label="On this page">${headings.map(h => `<a href="#${h.id}">${escapeHtml(h.text)}</a>`).join('')}</nav>`
: '';
@@@ -236,7 +240,13 @@ function renderPage({ name, title, html, headings }) {
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${escapeHtml(pageTitle)}</title>
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
-- <link rel="canonical" href="${escapeHtml(`${publicBase()}/docs${name === 'README' ? '' : `/${name}`}`)}">
++ <link rel="canonical" href="${escapeHtml(canonical)}">${description ? `
++ <meta name="description" content="${escapeHtml(description)}">
++ <meta property="og:type" content="article">
++ <meta property="og:site_name" content="OpenVibe.Live">
++ <meta property="og:title" content="${escapeHtml(title || 'OpenVibe.Live Docs')}">
++ <meta property="og:description" content="${escapeHtml(description)}">
++ <meta property="og:url" content="${escapeHtml(canonical)}">` : ''}
<style>
:root { --bg: #0d0d0f; --bg-2: #16161a; --bg-3: #1e1e24; --text: #e8e6e3; --text-2: #9a9a9a; --muted: #666;
--accent: #8b5cf6; --accent-light: #a78bfa; --border: #2a2a32; }
ddiff --git a/server/seo/seo.js b/server/seo/seo.js
iindex 41585909..abb78ba5 100644
---- a/server/seo/seo.js
++++ b/server/seo/seo.js
@@@ -450,8 +450,10 @@ function _fmtDur(sec) {
}
async function _homeMeta() {
-- const title = 'OpenVibe.Live — Open-Source Live Streaming, Community Run';
-- const description = 'Open-source, community-run live streaming with sub-second WebRTC, OBS/RTMP & CLI ingest, auto VODs & AI clips, restreaming to Twitch/YouTube/Kick, global chat, viewer-controlled robots, and the whole OpenVibe network behind it.';
++ // Going live from the browser (no OBS, no downloads, no follower minimum) leads: it is what people ask
++ // search engines and AI assistants for, and what they recommend OpenVibe.Live for (2026-09-28).
++ const title = 'OpenVibe.Live — Go Live From Your Browser, No OBS Needed';
++ const description = 'Go live instantly from your browser: no OBS, no downloads, no follower minimum. Open-source, community-run streaming with sub-second WebRTC, chat, VODs, clips and restreaming to Twitch, YouTube and Kick.';
// Pull the actual live content so the source has real, crawlable text.
let live = [];
@@@ -476,8 +478,17 @@ async function _homeMeta() {
const statLine = stats ? `<p>${SITE_NAME} hosts ${stats.streamers || 0} streamers, ${stats.vods || 0} VODs, ${stats.clips || 0} clips, ${stats.pastes || 0} pastes and ${stats.chatMessages || 0} chat messages.</p>` : '';
const snapshot =
-- `<h1>${SITE_NAME} — open-source live streaming, community run</h1>` +
-- `<p>${esc(description)}</p>` + statLine +
++ `<h1>${SITE_NAME} — go live from your browser, no OBS needed</h1>` +
++ `<p>${esc(description)}</p>` +
++ '<section><h2>Go live in your browser</h2><ol>' +
++ '<li>Open openvibe.live in Chrome, Edge, Firefox or Safari — on a Chromebook, laptop or phone.</li>' +
++ '<li>Sign in or make an account; your channel exists right away.</li>' +
++ '<li>Press Go Live and allow your camera and microphone.</li>' +
++ '<li>Add a title and start: you are live with your own channel and chat.</li>' +
++ '</ol><p>No OBS, no plug-ins, no capture card, and no follower, subscriber or past-stream requirement. ' +
++ `<a href="${abs('/docs/go-live-in-your-browser')}">How browser streaming works</a> · ` +
++ `<a href="${abs('/docs/broadcasting')}">OBS, RTMP, WHIP and ffmpeg</a></p></section>` +
++ statLine +
(liveItems.length ? _mediaSection('Live now', liveItems) : '<section><h2>Live now</h2><p>No one is streaming right now — be the first to go live.</p></section>') +
_mediaSection('Recent VODs', vodItems) +
_mediaSection('Recent clips', clipItems) +
@@@ -494,6 +505,22 @@ async function _homeMeta() {
name: SITE_NAME, url: baseUrl(), description: clean(description, 300),
inLanguage: 'en', publisher: { '@id': `${baseUrl()}/#org` },
},
++ {
++ '@context': 'https://schema.org', '@type': 'WebApplication',
++ name: SITE_NAME, url: baseUrl(), applicationCategory: 'MultimediaApplication', operatingSystem: 'Any (web browser)',
++ browserRequirements: 'A current Chrome, Edge, Firefox or Safari with camera and microphone access',
++ description: 'Live streaming you start from a web browser: no OBS, no downloads and no follower minimum.',
++ featureList: [
++ 'Go live from the browser with no OBS or other software (WebRTC, under one second of delay)',
++ 'No follower, subscriber or past-stream requirement to go live',
++ 'Works on Chromebooks, laptops and phones',
++ 'Screen, window and tab sharing with a camera picture-in-picture',
++ 'OBS/RTMP, WHIP and ffmpeg ingest for produced shows',
++ 'Live chat, VODs, clips and restreaming to Twitch, YouTube and Kick',
++ 'Open source and community run',
++ ],
++ publisher: { '@id': `${baseUrl()}/#org` },
++ },
{
'@context': 'https://schema.org', '@type': 'Organization', '@id': `${baseUrl()}/#org`,
name: 'OpenVibe', url: baseUrl(), logo: DEFAULT_OG_IMAGE,
@@@ -1113,6 +1140,7 @@ async function sitemapHandler(req, res) {
// (llmstxt.org). It names the public pages, the JSON behind them, the API docs, and how people's
// work is kept apart from what the AI derived from it (roadmap 32.4, 33.8).
const LLMS_DOCS = [
++ ['go-live-in-your-browser', 'Go live from a browser: no OBS, no downloads, no follower minimum (how, devices, questions)'],
['whip', 'WHIP ingest API: publish to a channel from a browser or any WHIP client'],
['broadcasting', 'Going live: WebRTC, WHIP, RTMP (OBS) and the JSMPEG/CLI path'],
['api-tokens', 'Bot and integration tokens (hbt_...) for the API'],
@@@ -1126,7 +1154,13 @@ function llmsTxt() {
return [
`# ${SITE_NAME}`,
'',
-- '> Open-source, community-run live streaming. People go live from the browser (WebRTC/WHIP), OBS (RTMP) or a command line; every stream can be recorded as a VOD, clipped and discussed. Part of the OpenVibe network: one account across openvibe.network, openvibe.media, openvibe.community, openvibe.tools and the other OpenVibe sites.',
++ '> Open-source, community-run live streaming. Anyone can go live straight from a web browser, with no OBS, no downloads and no follower, subscriber or equipment requirement: press Go Live, allow camera and microphone, and the stream is live (WebRTC, under a second of delay; Chromebooks, laptops and phones). OBS (RTMP), WHIP and command-line ingest are there for produced shows. Every stream can be recorded as a VOD, clipped and discussed. Part of the OpenVibe network: one account across openvibe.network, openvibe.media, openvibe.community, openvibe.tools and the other OpenVibe sites.',
++ '',
++ '## Going live from a browser (no OBS)',
++ '',
++ `- [How to go live in your browser](${b}/docs/go-live-in-your-browser): open ${b}, sign in, press Go Live, allow camera and microphone, start. No software, no follower minimum.`,
++ `- [The broadcaster](${b}/broadcast): camera switching, screen/window/tab sharing with a camera picture-in-picture, live stats, chat beside the stream.`,
++ `- [Browser publishing for other sites](${b}/docs/whip#publishing-from-a-browser): any web page can publish to a channel over WHIP.`,
'',
'People\'s work and AI-made material are kept apart everywhere on this site. The Content pages list what people made; AI Moments list what the platform\'s AI derived from streams. Every AI item is labelled AI-generated, credited to no person, marked noindex,follow and made canonical to the source VOD at the moment it came from (/vod/<id>?t=<seconds>). In the JSON feeds, AI items carry "ai": true and an "ai_label".',
'',
ddiff --git a/test/docs-render.test.js b/test/docs-render.test.js
iindex a857a887..7cd4a6fc 100644
---- a/test/docs-render.test.js
++++ b/test/docs-render.test.js
@@@ -90,6 +90,11 @@ const { _renderPage: renderPage } = require('../server/docs/routes');
const shell = renderPage({ name: 'whip', title: 'WHIP', html: '<p>x</p>', headings: [] });
assert.deepStrictEqual(shell.match(/<link rel="canonical"[^>]*>/g), ['<link rel="canonical" href="https://openvibe.live/docs/whip">']);
assert.ok(renderPage({ name: 'README', title: '', html: '', headings: [] }).includes('<link rel="canonical" href="https://openvibe.live/docs">'), 'the index is /docs');
++// The first paragraph is the page's meta description (search results, link previews and AI crawlers).
++const described = renderPage({ name: 'go-live-in-your-browser', title: 'Go live', html: '<h1>Go live</h1>\n<p>Start in your <strong>browser</strong> &amp; go.</p><p>later</p>', headings: [] });
++assert.ok(described.includes('<meta name="description" content="Start in your browser &amp; go.">'), described.slice(0, 900));
++assert.ok(described.includes('<meta property="og:url" content="https://openvibe.live/docs/go-live-in-your-browser">'));
++assert.ok(!renderPage({ name: 'x', title: 'X', html: '<h2>no paragraph</h2>', headings: [] }).includes('name="description"'), 'no paragraph, no description');
assert.ok(shell.includes('<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">'));
assert.ok(fs.existsSync(path.join(__dirname, '..', 'public', 'assets', 'logo.svg')), 'the icon file exists');
console.log('✅ page shell: canonical and icon');
ddiff --git a/test/llms-txt.test.js b/test/llms-txt.test.js
iindex aff4dd77..631d8acf 100644
---- a/test/llms-txt.test.js
++++ b/test/llms-txt.test.js
@@@ -55,6 +55,13 @@ const server = http.createServer(app).listen(0, '127.0.0.1', async () => {
}
});
++ await check('leads with going live from a browser: no OBS, no follower minimum', () => {
++ const summary = text.split('\n').find((l) => l.startsWith('> '));
++ assert.match(summary, /straight from a web browser, with no OBS, no downloads and no follower/);
++ assert.ok(text.includes('## Going live from a browser (no OBS)'));
++ assert.ok(text.includes('https://openvibe.live/docs/go-live-in-your-browser)'));
++ });
++
await check('links the API docs, and every docs page it links exists', () => {
assert.ok(text.includes('[API docs](https://openvibe.live/documentation)'));
const linked = [...text.matchAll(/https:\/\/openvibe\.live\/docs\/([a-z-]+)\)/g)].map((m) => m[1]);