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');