This commit is contained in:
Flatlogic Bot 2025-11-07 23:05:18 +00:00
parent 1496e8cc1d
commit eb2b31997e

View File

@ -28,6 +28,7 @@ document.addEventListener('DOMContentLoaded', function () {
const grantCameraPermissionBtn = document.getElementById('grant-camera-permission-btn');
const cameraDeviceSelection = document.getElementById('camera-device-selection');
const sceneCameraDeviceInput = document.getElementById('scene-camera-device-input');
const cameraErrorMessage = document.getElementById('camera-error-message');
// Data Store
let scenes = [];
@ -35,6 +36,7 @@ document.addEventListener('DOMContentLoaded', function () {
let activeProgramSceneId = null;
let sceneIdCounter = 0;
let activeStreams = {}; // To hold references to active MediaStream objects
let cameraPermissionGranted = false;
// --- STREAM MANAGEMENT ---
function stopStream(panel) {
@ -129,8 +131,6 @@ document.addEventListener('DOMContentLoaded', function () {
video.src = scene.value;
} else { // camera
try {
// A deviceId of '' can happen if the initial scene is a camera
// before permission is granted. We should ask for default camera.
const constraints = scene.value
? { video: { deviceId: { exact: scene.value } } }
: { video: true };
@ -181,55 +181,68 @@ document.addEventListener('DOMContentLoaded', function () {
}
// --- CAMERA & DEVICE MANAGEMENT ---
async function populateCameraDevices() {
// First, check for a secure context (HTTPS), which is required for getUserMedia.
function showCameraUI() {
cameraErrorMessage.textContent = '';
if (cameraPermissionGranted) {
cameraPermissionPrompt.style.display = 'none';
cameraDeviceSelection.style.display = 'block';
} else {
cameraPermissionPrompt.style.display = 'block';
cameraDeviceSelection.style.display = 'none';
}
}
async function requestCameraPermission() {
if (!window.isSecureContext) {
console.error("Camera access requires a secure context (HTTPS).");
cameraPermissionPrompt.innerHTML = '<p class="text-danger">Camera access is only available over a secure HTTPS connection. Please ensure you are using an https:// URL.</p>';
cameraErrorMessage.textContent = 'Camera access is only available over a secure HTTPS connection.';
return;
}
let stream = null;
try {
// Request permission first
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
// First, get a stream. This is necessary to trigger the permission prompt
// and to get the device labels.
stream = await navigator.mediaDevices.getUserMedia({ video: true });
// Stop the tracks immediately, we only needed it for permission
stream.getTracks().forEach(track => track.stop());
// Now that we have permission and an active stream, enumerate devices.
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
sceneCameraDeviceInput.innerHTML = '';
sceneCameraDeviceInput.innerHTML = ''; // Clear previous options
if (videoDevices.length === 0) {
// This case is unlikely if getUserMedia succeeded, but good to have.
cameraPermissionPrompt.innerHTML = '<p class="text-warning">No camera devices were found.</p>';
return;
cameraErrorMessage.textContent = 'No camera devices were found.';
return; // Exit if no cameras
}
// Populate the dropdown
videoDevices.forEach(device => {
const option = document.createElement('option');
option.value = device.deviceId;
// Use the device label if available, otherwise a generic name
option.textContent = device.label || `Camera ${sceneCameraDeviceInput.length + 1}`;
sceneCameraDeviceInput.appendChild(option);
});
// Switch UI
cameraPermissionPrompt.style.display = 'none';
cameraDeviceSelection.style.display = 'block';
// We have successfully populated the list, so update the UI
cameraPermissionGranted = true;
showCameraUI();
} catch (err) {
console.error("Could not get camera permissions or list devices:", err);
let errorMessage = 'An unexpected error occurred while trying to access the camera.';
console.error("Could not get camera permissions:", err);
let msg = 'An unexpected error occurred.';
if (err.name === 'NotAllowedError') {
errorMessage = 'Camera permission was denied. You need to grant permission in your browser\'s site settings to use this feature.';
msg = "Camera permission was denied. You need to grant permission in your browser's site settings to use this feature.";
} else if (err.name === 'NotFoundError') {
errorMessage = 'No camera was found on your device. Please connect a camera and try again.';
msg = 'No camera was found on your device.';
} else if (err.name === 'NotReadableError') {
errorMessage = 'The camera is currently in use by another application or a hardware error occurred.';
msg = 'The camera is currently in use by another application.';
}
cameraErrorMessage.textContent = msg;
} finally {
// Stop the stream that was used to get permissions and device labels.
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
cameraPermissionPrompt.innerHTML = `<p class="text-danger">${errorMessage}</p>`;
}
}
@ -257,14 +270,11 @@ document.addEventListener('DOMContentLoaded', function () {
stopStream(programPanel);
// Clone the content and style
programPanel.innerHTML = previewPanel.innerHTML;
programPanel.style.backgroundColor = previewPanel.style.backgroundColor;
programPanel.style.backgroundImage = previewPanel.style.backgroundImage;
const previewVideo = previewPanel.querySelector('video');
if (previewVideo && previewVideo.srcObject) { // It's a stream
// Move the stream from preview to program
if (activeStreams[previewPanel.id]) {
activeStreams[programPanel.id] = activeStreams[previewPanel.id];
delete activeStreams[previewPanel.id];
}
@ -272,7 +282,7 @@ document.addEventListener('DOMContentLoaded', function () {
programPanel.querySelector('.panel-label').textContent = 'PROGRAM (ON AIR)';
const programVideo = programPanel.querySelector('video');
if(programVideo) {
programVideo.muted = false; // Unmute in program
programVideo.muted = false;
}
clearPreview();
@ -281,7 +291,6 @@ document.addEventListener('DOMContentLoaded', function () {
function showModal() {
modal.style.display = 'flex';
sceneNameInput.focus();
// Reset and update visibility on open
sceneTypeSelect.dispatchEvent(new Event('change'));
}
function hideModal() { modal.style.display = 'none'; }
@ -298,18 +307,11 @@ document.addEventListener('DOMContentLoaded', function () {
cameraSettings.style.display = type === 'camera' ? 'block' : 'none';
if (type === 'camera') {
// Reset to initial state
cameraPermissionPrompt.style.display = 'block';
cameraDeviceSelection.style.display = 'none';
// Check if devices are already populated
if (sceneCameraDeviceInput.options.length > 0) {
cameraPermissionPrompt.style.display = 'none';
cameraDeviceSelection.style.display = 'block';
}
showCameraUI();
}
});
grantCameraPermissionBtn.addEventListener('click', populateCameraDevices);
grantCameraPermissionBtn.addEventListener('click', requestCameraPermission);
async function saveScene() {
const name = sceneNameInput.value.trim();
@ -362,13 +364,12 @@ document.addEventListener('DOMContentLoaded', function () {
const newScene = addScene(name, type, value);
updatePreview(newScene.id);
// Reset form and hide modal
sceneNameInput.value = '';
sceneColorInput.value = '#1e90ff';
sceneImageFileInput.value = '';
sceneVideoFileInput.value = '';
// Don't clear camera list, just reset selection
sceneTypeSelect.value = 'color';
colorSettings.style.display = 'block';
imageSettings.style.display = 'none';
videoSettings.style.display = 'none';
@ -387,9 +388,8 @@ document.addEventListener('DOMContentLoaded', function () {
// --- INITIALIZATION ---
function initialize() {
// Set a default scene that doesn't require permissions on load
addScene('Starting Soon Screen', 'image', 'https://images.pexels.com/photos/1762851/pexels-photo-1762851.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1');
addScene('Main Camera', 'camera', ''); // Default camera, will ask for permission on use
addScene('Main Camera', 'camera', '');
addScene('Promo Video', 'video', 'https://assets.mixkit.co/videos/preview/mixkit-spinning-around-the-earth-29351-large.mp4');
addScene('Screen Share', 'color', '#006633');