4
This commit is contained in:
parent
1496e8cc1d
commit
eb2b31997e
@ -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');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user