voice-poc: multi-share grid + pop-out window per shared screen
This commit is contained in:
1 parent
ebbf1b07d8
commit
a0d5a9a2c7
3 files changed
+87
-7
No files matched your search
+74
-6
@@ -536,14 +536,46 @@ function renderTiles() {
|
|||||||
renderStageEmpty();
|
renderStageEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderScreens() {
|
// share keys: 'me' or peer id; popped-out shares render in their own window
|
||||||
els.screens.innerHTML = '';
|
const poppedWindows = new Map(); // key -> {win, name}
|
||||||
|
|
||||||
|
function activeShares() {
|
||||||
const shares = [];
|
const shares = [];
|
||||||
if (S.sharing && S.screenStream) shares.push({ name: `${S.name} (you)`, stream: S.screenStream });
|
if (S.sharing && S.screenStream) shares.push({ key: 'me', name: `${S.name} (you)`, stream: S.screenStream });
|
||||||
for (const peer of S.peers.values()) {
|
for (const peer of S.peers.values()) {
|
||||||
if (peer.screenStream) shares.push({ name: peer.name, stream: peer.screenStream });
|
if (peer.screenStream) shares.push({ key: peer.id, name: peer.name, stream: peer.screenStream });
|
||||||
}
|
}
|
||||||
for (const share of shares) {
|
return shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderScreens() {
|
||||||
|
// prune popouts whose share ended
|
||||||
|
const live = new Set(activeShares().map((s) => s.key));
|
||||||
|
for (const [key, p] of [...poppedWindows]) {
|
||||||
|
if (!live.has(key)) {
|
||||||
|
try { p.win.close(); } catch {}
|
||||||
|
poppedWindows.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
els.screens.innerHTML = '';
|
||||||
|
els.screens.classList.toggle('multi', activeShares().length > 1);
|
||||||
|
for (const share of activeShares()) {
|
||||||
|
if (poppedWindows.has(share.key)) {
|
||||||
|
// rendered in its own window — show a small placeholder card to re-embed
|
||||||
|
const ph = document.createElement('div');
|
||||||
|
ph.className = 'screen-card popped';
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'pop-btn';
|
||||||
|
btn.textContent = '⧉ re-embed';
|
||||||
|
btn.onclick = () => closePopout(share.key);
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.className = 'label';
|
||||||
|
label.textContent = `${share.name} — popped out`;
|
||||||
|
ph.append(btn, label);
|
||||||
|
els.screens.appendChild(ph);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'screen-card';
|
card.className = 'screen-card';
|
||||||
const video = document.createElement('video');
|
const video = document.createElement('video');
|
||||||
@@ -556,11 +588,47 @@ function renderScreens() {
|
|||||||
label.className = 'label';
|
label.className = 'label';
|
||||||
label.innerHTML = '<span class="live">●</span>';
|
label.innerHTML = '<span class="live">●</span>';
|
||||||
label.appendChild(document.createTextNode(share.name));
|
label.appendChild(document.createTextNode(share.name));
|
||||||
card.append(video, label);
|
const pop = document.createElement('button');
|
||||||
|
pop.className = 'pop-btn';
|
||||||
|
pop.title = 'Pop out in new window';
|
||||||
|
pop.textContent = '⧉';
|
||||||
|
pop.onclick = () => popOutShare(share);
|
||||||
|
card.append(video, label, pop);
|
||||||
els.screens.appendChild(card);
|
els.screens.appendChild(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function popOutShare(share) {
|
||||||
|
if (poppedWindows.has(share.key)) return;
|
||||||
|
const win = window.open('', `share-${share.key}`, 'width=960,height=540');
|
||||||
|
if (!win) {
|
||||||
|
toast('Pop-out blocked by the browser — allow pop-ups for this page', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
win.document.write(`<!doctype html><title>${share.name} — screen share</title>
|
||||||
|
<style>html,body{margin:0;height:100%;background:#000;display:grid}video{width:100%;height:100%;object-fit:contain}</style>
|
||||||
|
<video autoplay playsinline></video>`);
|
||||||
|
win.document.close();
|
||||||
|
const v = win.document.querySelector('video');
|
||||||
|
v.srcObject = share.stream;
|
||||||
|
v.play().catch(() => {});
|
||||||
|
poppedWindows.set(share.key, { win, name: share.name });
|
||||||
|
win.addEventListener('beforeunload', () => {
|
||||||
|
poppedWindows.delete(share.key);
|
||||||
|
renderScreens();
|
||||||
|
});
|
||||||
|
renderScreens();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePopout(key) {
|
||||||
|
const p = poppedWindows.get(key);
|
||||||
|
if (p) {
|
||||||
|
poppedWindows.delete(key);
|
||||||
|
try { p.win.close(); } catch {}
|
||||||
|
renderScreens();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// spatial canvas
|
// spatial canvas
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -88,6 +88,6 @@
|
|||||||
|
|
||||||
<div id="toasts"></div>
|
<div id="toasts"></div>
|
||||||
|
|
||||||
<script src="app.js?v=5"></script>
|
<script src="app.js?v=6"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -106,6 +106,18 @@ body {
|
|||||||
#stage:not(.empty) #stage-empty { display: none; }
|
#stage:not(.empty) #stage-empty { display: none; }
|
||||||
|
|
||||||
#screens { display: flex; gap: 12px; flex-wrap: wrap; }
|
#screens { display: flex; gap: 12px; flex-wrap: wrap; }
|
||||||
|
#screens.multi { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); align-content: start; }
|
||||||
|
#screens.multi .screen-card { flex: none; }
|
||||||
|
.screen-card.popped {
|
||||||
|
min-height: 120px; display: grid; place-items: center;
|
||||||
|
background: var(--bg-0); box-shadow: none; border: 1px dashed var(--bg-4);
|
||||||
|
}
|
||||||
|
.pop-btn {
|
||||||
|
position: absolute; right: 8px; top: 8px; z-index: 2;
|
||||||
|
background: rgba(0,0,0,.65); color: var(--text-0); border: none; cursor: pointer;
|
||||||
|
border-radius: 5px; padding: 3px 8px; font-size: 13px; font-weight: 600;
|
||||||
|
}
|
||||||
|
.pop-btn:hover { background: var(--brand); }
|
||||||
.screen-card {
|
.screen-card {
|
||||||
position: relative; background: #000; border-radius: var(--radius); overflow: hidden;
|
position: relative; background: #000; border-radius: var(--radius); overflow: hidden;
|
||||||
flex: 1 1 480px; max-width: 100%; box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
flex: 1 1 480px; max-width: 100%; box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
||||||
|
|||||||
Reference in new issue
Block a user