Screen sharing #17
@@ -43,6 +43,9 @@ class ScreenShare {
|
|||||||
host = $state.raw<Session | null>(null);
|
host = $state.raw<Session | null>(null);
|
||||||
stream = $state.raw<MediaStream | null>(null);
|
stream = $state.raw<MediaStream | null>(null);
|
||||||
kind = $state<ShareKind>('screen');
|
kind = $state<ShareKind>('screen');
|
||||||
|
// Show our own camera mirrored to ourselves, as a mirror would. Not a camera pointing away
|
||||||
|
// from us (a phone's back camera), and never what the others get.
|
||||||
|
mirror = $state(false);
|
||||||
viewers = $state(0);
|
viewers = $state(0);
|
||||||
// Streams we receive: one screen at a time, and any number of cameras next to it
|
// Streams we receive: one screen at a time, and any number of cameras next to it
|
||||||
watches = $state.raw<Watching[]>([]);
|
watches = $state.raw<Watching[]>([]);
|
||||||
@@ -142,6 +145,7 @@ class ScreenShare {
|
|||||||
}
|
}
|
||||||
this.usesVirtualMic = usesVirtualMic;
|
this.usesVirtualMic = usesVirtualMic;
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
|
this.mirror = kind === 'camera' && stream.getVideoTracks()[0]?.getSettings().facingMode !== 'environment';
|
||||||
this.host = s;
|
this.host = s;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.send(s, this.others(s), SHARE.on, kind);
|
this.send(s, this.others(s), SHARE.on, kind);
|
||||||
|
|||||||
@@ -220,7 +220,7 @@
|
|||||||
<span class="label">Preview</span>
|
<span class="label">Preview</span>
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<!-- svelte-ignore a11y_media_has_caption -->
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
<video bind:this={video} autoplay playsinline muted></video>
|
<video bind:this={video} class:mirror={kind === 'camera' && preview?.getVideoTracks()[0]?.getSettings().facingMode !== 'environment'} autoplay playsinline muted></video>
|
||||||
{#if !preview}<p>Nothing chosen yet</p>{/if}
|
{#if !preview}<p>Nothing chosen yet</p>{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -280,6 +280,8 @@
|
|||||||
.name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.name { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.preview { position: relative; background: #000; aspect-ratio: 16 / 9; max-height: 240px; margin: 0 auto; width: 100%; }
|
.preview { position: relative; background: #000; aspect-ratio: 16 / 9; max-height: 240px; margin: 0 auto; width: 100%; }
|
||||||
.preview video { width: 100%; height: 100%; object-fit: contain; display: block; }
|
.preview video { width: 100%; height: 100%; object-fit: contain; display: block; }
|
||||||
|
/* Your own camera as a mirror shows you; a camera facing away is left as it is */
|
||||||
|
.preview video.mirror { transform: scaleX(-1); }
|
||||||
.preview p { position: absolute; inset: 0; margin: 0; display: flex; align-items: center; justify-content: center; color: #aaa; font-size: 13px; }
|
.preview p { position: absolute; inset: 0; margin: 0; display: flex; align-items: center; justify-content: center; color: #aaa; font-size: 13px; }
|
||||||
.check { display: flex; align-items: center; gap: 8px; text-transform: none; font-weight: 400; letter-spacing: 0; font-size: 14px; color: var(--text); margin-top: 12px; }
|
.check { display: flex; align-items: center; gap: 8px; text-transform: none; font-weight: 400; letter-spacing: 0; font-size: 14px; color: var(--text); margin-top: 12px; }
|
||||||
.check input { width: auto; }
|
.check input { width: auto; }
|
||||||
|
|||||||
@@ -267,7 +267,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="pic" role="presentation" oncontextmenu={streamMenu}
|
<div class="pic" role="presentation" oncontextmenu={streamMenu}
|
||||||
ontouchstart={touchStart} ontouchend={touchEnd} ontouchmove={touchEnd} ontouchcancel={touchEnd}>
|
ontouchstart={touchStart} ontouchend={touchEnd} ontouchmove={touchEnd} ontouchcancel={touchEnd}>
|
||||||
<video bind:this={spotVideo} use:media={focused} autoplay playsinline muted ondblclick={fullscreen}></video>
|
<video bind:this={spotVideo} class:mirror={focus === self.session && share.mirror} use:media={focused} autoplay playsinline muted ondblclick={fullscreen}></video>
|
||||||
{#if focus !== self.session && focusWatch && focusWatch.state !== 'live'}
|
{#if focus !== self.session && focusWatch && focusWatch.state !== 'live'}
|
||||||
<p class="pending" class:bad={focusWatch.state === 'failed'}>{focusWatch.state === 'failed'
|
<p class="pending" class:bad={focusWatch.state === 'failed'}>{focusWatch.state === 'failed'
|
||||||
? 'Could not connect to the stream. Across the internet both of you need a STUN server; networks that block direct connections need a relay. Both are in Settings, Voice.'
|
? 'Could not connect to the stream. Across the internet both of you need a STUN server; networks that block direct connections need a relay. Both are in Settings, Voice.'
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
aria-label={stream ? `${focus === u.session ? 'Hide' : 'Show'} the ${sharing || 'stream'} of ${store.displayName(u)}` : sharing && !isSelf ? `Watch the ${sharing} of ${store.displayName(u)}` : store.displayName(u)}>
|
aria-label={stream ? `${focus === u.session ? 'Hide' : 'Show'} the ${sharing || 'stream'} of ${store.displayName(u)}` : sharing && !isSelf ? `Watch the ${sharing} of ${store.displayName(u)}` : store.displayName(u)}>
|
||||||
{#if cam}
|
{#if cam}
|
||||||
<!-- svelte-ignore a11y_media_has_caption -->
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
<video class:mirror={isSelf} use:media={cam} autoplay playsinline muted></video>
|
<video class:mirror={isSelf && share.mirror} use:media={cam} autoplay playsinline muted></video>
|
||||||
{:else}
|
{:else}
|
||||||
<Avatar name={u.name} size={avatar} speaking={talking(u.session)} />
|
<Avatar name={u.name} size={avatar} speaking={talking(u.session)} />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
/* A camera keeps its own shape inside the tile, whatever size the tile is: nothing is cut
|
/* A camera keeps its own shape inside the tile, whatever size the tile is: nothing is cut
|
||||||
off, a phone held upright gets bars at the sides. Your own is mirrored, like a mirror. */
|
off, a phone held upright gets bars at the sides. Your own is mirrored, like a mirror. */
|
||||||
.tile video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; }
|
.tile video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; }
|
||||||
.tile video.mirror { transform: scaleX(-1); }
|
.tile video.mirror, .spot video.mirror { transform: scaleX(-1); }
|
||||||
.icon-btn.live, .icon-btn.live:hover { color: var(--speaking); }
|
.icon-btn.live, .icon-btn.live:hover { color: var(--speaking); }
|
||||||
.camgrid { position: relative; display: flex; flex-direction: column; flex: none; }
|
.camgrid { position: relative; display: flex; flex-direction: column; flex: none; }
|
||||||
/* Name tag over the lower left corner */
|
/* Name tag over the lower left corner */
|
||||||
|
|||||||
Reference in New Issue
Block a user