openvibers/openrestream Commits

The home states the free tier in numbers (market outcome) (#163)

OP OpenVibers committed 12 days ago · b60562305f · 3 files, +30 −1

'The free tier, in numbers': $0 with no paid tier, up to 10 destinations a stream (from the
service's own config), a stream from OBS copied and never re-encoded, no watermark, RTMP in (or a
browser on OpenVibe.Live), RTMP/RTMPS/SRT out, recording per stream into OpenVibe.Video, and no
minimum. OpenRestream outcome 1: the real limits, not 'free'.

Changes

ddiff --git a/server/ui/layout.js b/server/ui/layout.js
iindex 720af0b..b0dcef3 100644
---- a/server/ui/layout.js
++++ b/server/ui/layout.js
@@@ -31,6 +31,9 @@ main.ore .sc-hero,main.ore .sc-sec,main.ore .sc-cta{padding-left:0;padding-right
.ore .card{background:var(--ore-panel);border:1px solid var(--ore-line);border-radius:10px;padding:14px 16px;margin:12px 0}
.ore-stats{margin:0;display:grid;gap:10px}.ore-stats div{border:1px solid var(--ore-line);border-radius:12px;padding:12px 16px;background:var(--bg-card,#131c2e)}
.ore-stats dt{font-size:1.25rem;font-weight:700}.ore-stats dd{margin:2px 0 0;color:var(--ore-muted);font-size:.92rem}
++.ore-tier-grid{margin:14px 0 0;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:1px;border:1px solid var(--ore-line);border-radius:14px;overflow:hidden;background:var(--ore-line)}
++.ore-tier-grid div{padding:12px 16px;background:var(--bg-card,#131c2e)}.ore-tier-grid dt{font-size:.74rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--ore-muted)}
++.ore-tier-grid dd{margin:4px 0 0;font-size:.95rem;line-height:1.45}
.ore-faq details{border-bottom:1px solid var(--ore-line);padding:.75rem 0}.ore-faq details:first-of-type{border-top:1px solid var(--ore-line)}
.ore-faq summary{cursor:pointer;font-weight:600;list-style-position:inside}.ore-faq summary:hover{color:#93c5fd}.ore-faq details p{margin:.5rem 0 0;color:var(--ore-muted);max-width:46rem}
.ore table{width:100%;border-collapse:collapse;font-size:.92rem}.ore th,.ore td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--ore-line);vertical-align:top}
ddiff --git a/server/ui/routes.js b/server/ui/routes.js
iindex af9fd0a..3d10900 100644
---- a/server/ui/routes.js
++++ b/server/ui/routes.js
@@@ -167,6 +167,26 @@ ${frame.shipped({ service: 'openre', title: 'Recently shipped on OpenRestream' }
return `rtmp://${host}${r.publicPort && Number(r.publicPort) !== 1935 ? `:${r.publicPort}` : ''}/live`;
};
++ /**
++ * The free tier, in numbers (market outcome 1: state the real limits, not "free"): every figure is what the service
++ * does today, the destination cap read from its own config, so the page can never promise more than it runs.
++ */
++ function freeTier() {
++ const max = Number(config.outputs && config.outputs.maxPerStream) || 10;
++ const rows = [
++ ['Price', '$0. There is no paid tier'],
++ ['Destinations', `Up to ${max} a stream, each with its own health, logs and retries`],
++ ['Quality', 'What you send: a stream from OBS is copied, never re-encoded'],
++ ['Watermark', 'None'],
++ ['In', 'RTMP from OBS or any encoder; or your browser, on OpenVibe.Live'],
++ ['Out', 'RTMP, RTMPS and SRT'],
++ ['Recording', 'Per stream: a VOD, clips only, or nothing, saved to your OpenVibe.Video'],
++ ['Minimum', 'None: no follower, subscriber or eligibility threshold'],
++ ];
++ return `<section class="sc-sec ore-tier" aria-labelledby="ore-tier-h"><h2 id="ore-tier-h">The free tier, in numbers</h2>
++<dl class="ore-tier-grid">${rows.map(([k, v]) => `<div><dt>${esc(k)}</dt><dd>${esc(v)}</dd></div>`).join('')}</dl></section>`;
++ }
++
function frontPage(cfg, f) {
const live = cfg.liveUrl;
const n = (v) => Number(v).toLocaleString('en-US');
@@@ -197,7 +217,7 @@ ${frame.shipped({ service: 'openre', title: 'Recently shipped on OpenRestream' }
{ icon: 'ov:code', title: 'Open API and SDK', text: 'Streams, keys, sessions and destinations through the OpenVibe API and SDK, for your own tools and bots.' },
{ icon: 'ov:community', title: 'Free, open, no minimum', text: 'Any OpenVibe account can restream from the first minute: no follower, subscriber or eligibility threshold, and the code is on GitHub.' },
],
-- }) + showcase.features({
++ }) + freeTier() + showcase.features({
id: 'destinations',
title: 'Where it can stream',
lede: 'Add each destination once, test it, and it goes live with you every time.',
ddiff --git a/test/discovery.test.js b/test/discovery.test.js
iindex 7035f03..c0ff1f4 100644
---- a/test/discovery.test.js
++++ b/test/discovery.test.js
@@@ -73,6 +73,12 @@ t('the front page leads with open restreaming, links to Live for browser go-live
const r = await request(api.base, 'GET', '/');
assert.strictEqual(r.status, 200);
assert.match(r.text, /<h1>Go live once\.<span class="sc-accent"> Stream everywhere\.<\/span><\/h1>/);
++ // The free tier's real limits, as the service runs them (market outcome 1): the cap from its own config.
++ const tier = (/<section class="sc-sec ore-tier"[\s\S]*?<\/section>/.exec(r.text) || [''])[0];
++ assert.match(tier, /<h2 id="ore-tier-h">The free tier, in numbers<\/h2>/);
++ assert.match(tier, /<dt>Destinations<\/dt><dd>Up to 10 a stream, each with its own health, logs and retries<\/dd>/);
++ assert.match(tier, /<dt>Quality<\/dt><dd>What you send: a stream from OBS is copied, never re-encoded<\/dd>/);
++ assert.match(tier, /<dt>Out<\/dt><dd>RTMP, RTMPS and SRT<\/dd>/);
assert.match(r.text, /<meta name="description" content="Go live once from OBS and restream to Twitch, YouTube, Kick and any RTMP or SRT server at once/);
const desc = /<meta name="description" content="([^"]*)"/.exec(r.text)[1];
assert.ok(desc.length <= 160, `the description fits a search result (${desc.length})`);