194 lines
6.0 KiB
HTML
194 lines
6.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Media Transport — Architecture</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
display: flex;
|
|
height: 100vh;
|
|
font-family: monospace;
|
|
background: #1e1e2e;
|
|
color: #cdd6f4;
|
|
}
|
|
|
|
nav {
|
|
width: 220px;
|
|
min-width: 220px;
|
|
background: #181825;
|
|
border-right: 1px solid #313244;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 1rem 0;
|
|
}
|
|
|
|
nav h1 {
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
color: #6c7086;
|
|
padding: 0 1rem 0.75rem;
|
|
border-bottom: 1px solid #313244;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
nav a {
|
|
display: block;
|
|
padding: 0.5rem 1rem;
|
|
color: #cdd6f4;
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
border-left: 3px solid transparent;
|
|
transition: background 0.1s, border-color 0.1s;
|
|
}
|
|
|
|
nav a:hover { background: #313244; }
|
|
nav a.active { border-left-color: #89b4fa; color: #89b4fa; background: #1e2d3e; }
|
|
|
|
nav .subtitle {
|
|
font-size: 0.7rem;
|
|
color: #6c7086;
|
|
padding: 0 1rem;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
nav .phase-badge {
|
|
font-size: 0.65rem;
|
|
color: #a6e3a1;
|
|
float: right;
|
|
}
|
|
|
|
nav .section {
|
|
font-size: 0.65rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
color: #6c7086;
|
|
padding: 1rem 1rem 0.25rem;
|
|
}
|
|
|
|
main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
header {
|
|
padding: 0.75rem 1.25rem;
|
|
background: #181825;
|
|
border-bottom: 1px solid #313244;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
header h2 { font-size: 0.95rem; }
|
|
header .desc { font-size: 0.75rem; color: #6c7086; }
|
|
|
|
.viewer {
|
|
flex: 1;
|
|
overflow: auto;
|
|
padding: 1.5rem;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
background: #1e1e2e;
|
|
}
|
|
|
|
.viewer object,
|
|
.viewer img {
|
|
max-width: 100%;
|
|
border-radius: 6px;
|
|
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
|
|
}
|
|
|
|
.placeholder {
|
|
color: #6c7086;
|
|
font-size: 0.85rem;
|
|
margin-top: 4rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<nav>
|
|
<h1>Media Transport</h1>
|
|
|
|
<div class="section">Workspace</div>
|
|
<a href="#" data-svg="crates.svg" data-title="Crate Dependency Graph" data-desc="Workspace members and external deps">
|
|
Crate graph
|
|
</a>
|
|
|
|
<div class="section">Client (sender)</div>
|
|
<a href="#" data-svg="client-pipeline.svg" data-title="Client Pipeline" data-desc="KMS capture + PulseAudio → VAAPI H.264 + AAC → TCP transport">
|
|
Pipeline
|
|
</a>
|
|
|
|
<div class="section">Server (receiver)</div>
|
|
<a href="#" data-svg="server-pipeline.svg" data-title="Server Pipeline" data-desc="fMP4 recording, UDP live relay, scene detection (UDS → Python), audio extraction">
|
|
Pipeline
|
|
</a>
|
|
|
|
<div class="section">Status</div>
|
|
<a href="#" data-svg="" data-title="Current State (2026-04-10)" data-desc="Architecture status and known regressions"
|
|
onclick="event.preventDefault(); document.querySelectorAll('nav a').forEach(l=>l.classList.remove('active')); this.classList.add('active'); document.getElementById('title').textContent=this.dataset.title; document.getElementById('desc').textContent=this.dataset.desc; document.getElementById('viewer').innerHTML=document.getElementById('status-content').innerHTML; return false;">
|
|
State & regressions
|
|
</a>
|
|
</nav>
|
|
|
|
<template id="status-content">
|
|
<div style="max-width:720px; font-size:0.85rem; line-height:1.6; color:#cdd6f4">
|
|
<h3 style="color:#f38ba8; margin-bottom:0.5rem">Scene detection regressed</h3>
|
|
<p>In the Python-only pipeline, scene detection was a branch of the <b>same ffmpeg process</b> that records (fMP4 + UDP relay + CUDA decode + select filter). The flush trick worked because all outputs shared one decoder.</p>
|
|
<p style="margin-top:0.5rem">After Rust took over transport, scene detection became a <b>separate ffmpeg</b> fed via <code>scene.sock</code> Unix socket relay. Different buffering semantics broke the "one behind" flush fix, and <code>try_send</code> drops cause decoder corruption until the next keyframe.</p>
|
|
<h3 style="color:#a6e3a1; margin:1rem 0 0.5rem">Working fallback</h3>
|
|
<p>The Python-only path (<code>StreamRecorder</code> + <code>SessionProcessor</code>) still exists. <code>lifecycle.start(rust_transport=False)</code> bypasses Rust transport entirely. Plan: restore this as the default, keep Rust opt-in.</p>
|
|
<h3 style="color:#89b4fa; margin:1rem 0 0.5rem">What Rust transport got right</h3>
|
|
<ul style="padding-left:1.2rem">
|
|
<li>Connect time: 20s → 3s</li>
|
|
<li>Session reload: 1-2s</li>
|
|
<li>Custom framed protocol with reconnection support</li>
|
|
<li>Clean fMP4 recording + UDP live relay</li>
|
|
</ul>
|
|
<h3 style="color:#cba6f7; margin:1rem 0 0.5rem">Next: scene detection back into server ffmpeg</h3>
|
|
<p>Add scene detection as a third output of the Rust server's ffmpeg command (decode + select filter + MJPEG pipe) instead of relaying raw H.264 to a separate process. See <code>def/10-scene-detect-to-rust.md</code>.</p>
|
|
</div>
|
|
</template>
|
|
|
|
<main>
|
|
<header>
|
|
<h2 id="title">Select a diagram</h2>
|
|
<span class="desc" id="desc"></span>
|
|
</header>
|
|
<div class="viewer" id="viewer">
|
|
<p class="placeholder">← pick a diagram from the sidebar</p>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
const viewer = document.getElementById('viewer');
|
|
const titleEl = document.getElementById('title');
|
|
const descEl = document.getElementById('desc');
|
|
|
|
document.querySelectorAll('nav a').forEach(link => {
|
|
link.addEventListener('click', e => {
|
|
e.preventDefault();
|
|
document.querySelectorAll('nav a').forEach(l => l.classList.remove('active'));
|
|
link.classList.add('active');
|
|
|
|
titleEl.textContent = link.dataset.title;
|
|
descEl.textContent = link.dataset.desc;
|
|
|
|
// Use <object> so SVG internal text/links work
|
|
viewer.innerHTML = `<object type="image/svg+xml" data="${link.dataset.svg}"></object>`;
|
|
});
|
|
});
|
|
|
|
// Auto-select first
|
|
document.querySelector('nav a').click();
|
|
</script>
|
|
</body>
|
|
</html>
|