zebra-report: deploy — camera orientation re-acquire on rotate

This commit is contained in:
russell@unturf.com 2026-06-02 19:01:13 -04:00
parent 2b37b398e4
commit d6b28e6c78
No known key found for this signature in database

View file

@ -2252,6 +2252,68 @@ async function sfuUnpublishCamera(){
try { sessionStorage.removeItem(ACTIVE_CAM_KEY); } catch(_){}
}
/* On mobile the camera sensor's natural read-out orientation is locked
* at getUserMedia() time. Rotating the phone does NOT update the encoded
* frames' orientation — listeners see whatever was captured initially,
* regardless of how the publisher is holding the device now.
*
* Fix: when the device orientation changes while we're publishing, re-
* acquire the camera so the sensor reads out in the new orientation.
* We swap the track in the existing publisher's RTCRtpSender via
* replaceTrack — no SDP renegotiation, no SFU-side supplant, no
* subscriber renegotiation. Subscribers just start receiving frames
* in the new orientation a few hundred ms later.
*
* Debounced 350ms so a fast orientation flick doesn't trigger two
* back-to-back captures. */
let cameraOrientationDebounce = null;
async function reacquireCameraForOrientation(){
if (!sfuCameraPC || !sfuCameraStream || !myKeys) return;
const constraints = {
width: { ideal: 1280 }, height: { ideal: 720 },
aspectRatio: { ideal: 16/9 },
frameRate: { ideal: 30 },
};
if (cameraDeviceId) constraints.deviceId = { exact: cameraDeviceId };
let fresh;
try { fresh = await navigator.mediaDevices.getUserMedia({ video: constraints, audio: false }); }
catch(e){ logLine('err','camera re-acquire failed: '+e.message); return; }
const newVideo = fresh.getVideoTracks()[0];
if (!newVideo){ fresh.getTracks().forEach(t=>t.stop()); return; }
newVideo.contentHint = 'motion';
newVideo.addEventListener('ended', () => { sfuUnpublishCamera(); });
/* swap the sender's track */
let swapped = false;
for (const s of sfuCameraPC.getSenders()){
if (s.track && s.track.kind === 'video'){
try { await s.replaceTrack(newVideo); swapped = true; break; }
catch(e){ logLine('err','replaceTrack: '+e.message); }
}
}
if (!swapped){ fresh.getTracks().forEach(t=>t.stop()); return; }
/* stop the old stream's tracks AFTER swap so the encoder has the
* new source ready before the old one ends */
const old = sfuCameraStream;
sfuCameraStream = fresh;
if (old) old.getTracks().forEach(t=>t.stop());
renderCameraTile(myKeys.pubHex, fresh, { local: true });
const w = newVideo.getSettings ? (newVideo.getSettings().width||'?') : '?';
const h = newVideo.getSettings ? (newVideo.getSettings().height||'?') : '?';
logLine('', 'camera re-captured for new orientation ('+w+'x'+h+')');
}
function onCameraOrientationChange(){
if (cameraOrientationDebounce) clearTimeout(cameraOrientationDebounce);
cameraOrientationDebounce = setTimeout(() => {
cameraOrientationDebounce = null;
reacquireCameraForOrientation();
}, 350);
}
if (window.screen && window.screen.orientation && window.screen.orientation.addEventListener){
window.screen.orientation.addEventListener('change', onCameraOrientationChange);
} else {
window.addEventListener('orientationchange', onCameraOrientationChange);
}
async function sfuUnpublish(){
if (!sfuPubPC) return;
const pid = sfuPubPeerID;
@ -3709,8 +3771,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> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">ac20654825a05d94fc8840fc14a7ccec</span><br>
sha256 <span class="stamp-sha">4d7d6235f401636cb12b323236d4a0381a0d82a1af7e3c1bb2854a8d725a4572</span><br>
md5 <span class="stamp-md5">d6220b4e18f5dbe18bcce34b3762e67b</span><br>
sha256 <span class="stamp-sha">eae5efabeda7df668ae9b10cd962d49e2bc3dfb370fa38041a373591afcf3e4e</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>