zebra-spaces: 'share gameplay' on spotlit game tile — publishes the tab as a screen-share
Speakers can now broadcast their gameplay to the room. The spotlit
game tile's meta bar gets a 'share gameplay' button (next to where
fullscreen/etc live for other tile kinds). Click:
- sfuPublishScreen({ preferCurrentTab: true }) — Chromium picker
auto-selects the current tab; Firefox ignores the hint and shows
the normal picker (user just clicks 'this tab')
- existing SFU screen-share pipeline takes over from there — listeners
and other speakers see the tab via their subscribe leg, with the
game iframe visible at full size in their spotlight
- button label flips to 'stop sharing' while live; click again unpubs
Only canSpeak(myRole) gets the button — listeners are audience only.
The game iframe still loads + plays for the local user the same way
(the share is just a screen-capture of what's already on screen), so
there's no double-iframe or weird audio routing. Audience sees a
screen-share with the game running in real time.
This commit is contained in:
parent
e3a62be500
commit
5c26bc3232
1 changed files with 34 additions and 7 deletions
|
|
@ -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');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">aec8e1a791c0723464e7405f71b11ae7</span><br>
|
||||
sha256 <span class="stamp-sha">baa9354fbc0ff538dcb65e510ae7c6e1b01a5032e8e44c8ff51b0c76f821f4cf</span><br>
|
||||
md5 <span class="stamp-md5">7a6f4dd81ede2811d219eabc4603f026</span><br>
|
||||
sha256 <span class="stamp-sha">e728807c95404767e824dba0a87e9ad8446d739316c0c8f809e4befc0b9566d5</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue