Add screen sharing between mumh5 users (picture only)

- Signals travel as Mumble plugin data (compressed, chunked under Murmur's
  1000 byte limit); the picture goes directly to each viewer over WebRTC
- Toolbar button with a source picker in the desktop app, an indicator next to
  people who share, click to watch, a view above the chat with full screen
- Late joiners learn about running streams; moving, stopping or disconnecting
  ends them for viewers
- A note about IP addresses before the first use, optional STUN server setting
- Unit tests for the signal codec and an E2E with two app instances

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 20:58:33 +02:00
co-authored by Claude Opus 5.5
parent 82972438fe
commit 0cce3c97c2
23 changed files with 756 additions and 4 deletions
+14
View File
@@ -9,6 +9,7 @@
import { PERM } from '../core/client.ts';
import ResizeHandle from './ResizeHandle.svelte';
import { voice } from '../lib/audio/voice.svelte.ts';
import { share } from '../lib/share.svelte.ts';
// resizable: false in the phone drawer, which keeps a fixed width
// handleEdge: which side the resize handle sits on (left when the list is on the right, classic layout)
@@ -50,6 +51,14 @@
ui.panelOpen = true;
}
// Sharing runs on one server at a time; the button stops it from anywhere
const sharingHere = $derived(!!share.stream);
const canShare = $derived(session.status === 'connected' && share.supported(sessions.active));
function toggleShare() {
if (share.stream) share.stop();
else share.start(sessions.active);
}
// One button for the whole tree: open everything, then close everything
let allOpen = $state(false);
function toggleAll() {
@@ -159,6 +168,10 @@
onclick={() => session.setSelfDeaf(!deafened)} oncontextmenu={e => openQuick(e, 'output')}>
<Icon name={deafened ? 'headphones-off' : 'headphones'} size={18} />
</button>
<button class="icon-btn" class:live={sharingHere} title={sharingHere ? 'Stop sharing your screen' : 'Share your screen with your channel'}
aria-label={sharingHere ? 'Stop sharing your screen' : 'Share your screen'} disabled={!sharingHere && !canShare} onclick={toggleShare}>
<Icon name="screen" size={18} />
</button>
<span class="sep"></span>
<button class="icon-btn" title="Change your description" aria-label="Change your description" disabled={!self} onclick={editDescription}>
<Icon name="message" size={18} />
@@ -216,5 +229,6 @@
.who strong, .who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who span { font-size: 12px; color: var(--text-dim); }
.info, .info:hover { color: var(--warn); }
.live, .live:hover { color: var(--speaking); }
.offline-who { flex: 1 1 110px; padding: 2px 2px 2px 6px; color: var(--text-dim); }
</style>