Spaces:
Running
Running
File size: 6,113 Bytes
c001f24 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
<div class="card bg-dark text-white mb-3 border-secondary" id="camera-receiver-component" style="display: none;">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-camera-video me-2"></i>Remote Camera</h5>
<button type="button" class="btn-close btn-close-white" aria-label="Close" onclick="toggleCameraReceiver()"></button>
</div>
<div class="card-body text-center">
<div class="mb-3">
<div class="d-inline-block p-2 bg-white rounded mb-2">
<!-- QR Code Placeholder - In real app, generate this dynamically -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data={{ request.url_root }}camera_mobile" alt="Scan to connect mobile camera">
</div>
<p class="small text-muted mb-0">Scan with your phone or open <code>/camera_mobile</code></p>
</div>
<div class="position-relative d-inline-block w-100" style="max-width: 640px; min-height: 240px; background: #000; border-radius: 8px; overflow: hidden;">
<video id="webcamFeed" autoplay playsinline style="width: 100%; height: 100%; object-fit: contain;"></video>
<div id="cam-status" class="position-absolute top-50 start-50 translate-middle text-white bg-dark bg-opacity-75 p-2 rounded">
Waiting for connection...
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-success btn-lg" id="captureBtn" disabled>
<i class="bi bi-camera me-2"></i>Capture & Add
</button>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
<script>
let socket;
let peerConnection;
let receiverStream;
const videoEl = document.getElementById('webcamFeed');
const captureBtn = document.getElementById('captureBtn');
const statusEl = document.getElementById('cam-status');
const room = 'stream_room';
const config = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
function initCameraReceiver() {
if (socket) return; // Already initialized
socket = io();
socket.on('connect', () => {
console.log("Receiver connected");
socket.emit('join', { room: room });
statusEl.textContent = "Waiting for mobile camera...";
});
socket.on('offer', async (offer) => {
console.log("Received offer");
statusEl.textContent = "Connecting...";
if (peerConnection) peerConnection.close();
peerConnection = new RTCPeerConnection(config);
peerConnection.onicecandidate = (event) => {
if (event.candidate) socket.emit('candidate', { candidate: event.candidate, room: room });
};
peerConnection.ontrack = (event) => {
console.log("Received track");
receiverStream = event.streams[0];
videoEl.srcObject = receiverStream;
statusEl.style.display = 'none';
captureBtn.disabled = false;
};
await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit('answer', { answer: answer, room: room });
});
socket.on('candidate', async (candidate) => {
if (peerConnection) {
try { await peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); }
catch (e) { console.error(e); }
}
});
socket.on('trigger_capture', () => {
console.log("Remote capture triggered");
if (!captureBtn.disabled) {
captureBtn.click();
}
});
}
// Capture Logic
captureBtn.addEventListener('click', () => {
if (!receiverStream) return;
const canvas = document.createElement('canvas');
canvas.width = videoEl.videoWidth;
canvas.height = videoEl.videoHeight;
canvas.getContext('2d').drawImage(videoEl, 0, 0);
canvas.toBlob(blob => {
const file = new File([blob], `capture_${Date.now()}.png`, { type: 'image/png' });
// Add to the main upload form's FileList logic
// Since we can't programmatically modify file input value directly to add files easily without DataTransfer,
// we will handle this by creating a "virtual" file list or uploading immediately.
// Strategy: Upload immediately to a temporary staging area or just use the upload_images route directly for single file?
// The user wants to "reuse this component".
// Let's assume we just append it to a global list or upload it.
// We'll hook into the existing file processing logic of the parent page if possible.
// Or simpler: Add to a DataTransfer object and update the file input.
addFileToInput(file);
// Visual feedback
const originalText = captureBtn.innerHTML;
captureBtn.innerHTML = '<i class="bi bi-check-lg"></i> Added';
setTimeout(() => captureBtn.innerHTML = originalText, 1000);
}, 'image/png');
});
// Helper to add file to the main input
function addFileToInput(file) {
const input = document.getElementById('images-upload');
const dt = new DataTransfer();
// Add existing files
if (input.files) {
for (let i = 0; i < input.files.length; i++) {
dt.items.add(input.files[i]);
}
}
// Add new file
dt.items.add(file);
input.files = dt.files;
// Trigger change event to update UI
input.dispatchEvent(new Event('change'));
}
</script>
|