zebra-report: deploy — camera orientation re-acquire on rotate
This commit is contained in:
parent
2b37b398e4
commit
d6b28e6c78
1 changed files with 64 additions and 2 deletions
|
|
@ -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> · 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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue