diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index d5c82b9..b72c079 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -1701,7 +1701,29 @@ function setSpotlight(kind, pubHex){ iframe.loading = 'eager'; const meta = document.createElement('div'); meta.className = 'screen-meta'; const who = document.createElement('span'); who.textContent = 'game: ' + g.label; - meta.appendChild(who); + const ctl = document.createElement('span'); + /* 'share gameplay' captures the user's current tab via getDisplayMedia + * and publishes it through the same SFU screen-share path so audience + * members see the game live. Chromium's preferCurrentTab hint makes + * the picker default to this tab. Only speakers can publish — listeners + * can still watch the host's gameplay if someone shares. */ + if (canSpeak(myRole)){ + const shareBtn = document.createElement('button'); + shareBtn.className = 'small'; + const refreshLabel = () => { shareBtn.textContent = sfuScreenPC ? 'stop sharing' : 'share gameplay'; }; + refreshLabel(); + shareBtn.onclick = (ev) => { + ev.stopPropagation(); + if (sfuScreenPC){ + sfuUnpublishScreen().then(refreshLabel); + } else { + logLine('', 'share gameplay: pick this tab in the picker'); + sfuPublishScreen({ preferCurrentTab: true }).then(refreshLabel).catch(()=>refreshLabel()); + } + }; + ctl.appendChild(shareBtn); + } + meta.appendChild(who); meta.appendChild(ctl); big.appendChild(iframe); big.appendChild(meta); $('spotlight').appendChild(big); const thumb = thumbElementFor('game', pubHex); @@ -1865,7 +1887,7 @@ async function sfuPublish(){ } /* ----- screen share ----- */ -async function sfuPublishScreen(){ +async function sfuPublishScreen(opts){ if (sfuScreenPC || !myKeys || !roomID) return; let stream; try { @@ -1874,11 +1896,16 @@ async function sfuPublishScreen(){ * gracefully; nothing is rejected. The constraint matters for the audio * side: without channelCount:2 + sampleRate:48000, getDisplayMedia on * Chrome can hand back mono 16kHz, which kills music quality. */ - stream = await navigator.mediaDevices.getDisplayMedia({ + const constraints = { video: { width:{ideal:1920}, height:{ideal:1080}, frameRate:{ideal:30} }, audio: { echoCancellation:false, noiseSuppression:false, autoGainControl:false, - channelCount:2, sampleRate:48000 } - }); + channelCount:2, sampleRate:48000 }, + }; + /* Chromium-only hint — when sharing a game we want the picker to + * default to the current tab so the user can just click 'share' + * and the iframe content (gameplay) goes out without hunting. */ + if (opts && opts.preferCurrentTab) constraints.preferCurrentTab = true; + stream = await navigator.mediaDevices.getDisplayMedia(constraints); } catch(e){ logLine('err','screen share cancelled: '+e.message); return; } sfuScreenStream = stream; const vTracks = stream.getVideoTracks(), aTracks = stream.getAudioTracks(); @@ -3380,8 +3407,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');