Screen sharing: start dialog, sound, video tiles and a meeting view

- Start dialog with source list, live preview, sound and quality choices
- Sound on Linux through PipeWire: one program or everything except this
  app, fed into a virtual microphone and kept linked as programs come and go;
  system loopback on Windows; stereo Opus
- A sharer's tile shows the picture; clicking it opens a large view with the
  other tiles as a strip. Own stream is previewed in the own tile
- Stream volume for viewers, watch and stop from the profile panel
- Unit tests for the PipeWire link planner; the proxy test no longer writes to
  the root channel other tests listen in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 21:19:29 +02:00
co-authored by Claude Opus 5.5
parent 0cce3c97c2
commit 3b23f69a5f
20 changed files with 744 additions and 185 deletions
+15
View File
@@ -13,6 +13,8 @@
import { voice } from '../lib/audio/voice.svelte.ts';
import { renderIncoming } from '../lib/html.ts';
import RichEditor from './RichEditor.svelte';
import { share } from '../lib/share.svelte.ts';
import { sessions } from '../lib/session.svelte.ts';
let { onnavigate, onclose }: { onnavigate: () => void; onclose: () => void } = $props();
@@ -124,6 +126,16 @@
</div>
</div>
{#if session.sharing[user.session]}
<div class="sharing">
<Icon name="screen" size={16} />
<span>{isSelf ? 'You are sharing your screen' : 'Sharing their screen'}</span>
{#if isSelf}<button class="btn" onclick={() => share.stop()}>Stop sharing</button>
{:else if share.watching?.session === user.session}<button class="btn" onclick={() => share.unwatch()}>Stop watching</button>
{:else}<button class="btn primary" onclick={() => share.watch(sessions.active, user.session)}>Watch</button>{/if}
</div>
{/if}
{#if isSelf}
<div class="actions"><button class="btn" onclick={() => changeNamePrompt(user)}>Change name</button></div>
{/if}
@@ -158,6 +170,9 @@
</aside>
<style>
.sharing { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding: 8px 10px; font-size: 13px; color: var(--speaking); border: 1px solid var(--line); background: var(--bg-0); }
.sharing span { flex: 1; min-width: 0; color: var(--text); }
.sharing .btn { padding: 4px 10px; font-size: 12px; }
.panel { position: relative; max-width: 100%; height: 100%; flex: none; display: flex; flex-direction: column; background: var(--bg-1); border-left: 1px solid var(--line); min-height: 0; }
header { height: 52px; flex: none; display: flex; align-items: center; gap: 8px; padding: 0 8px 0 14px; border-bottom: 1px solid var(--line); color: var(--text-dim); }
h2 { flex: 1; min-width: 0; margin: 0; font-size: 15px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }