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 grantCameraPermissionBtn = document.getElementById('grant-camera-permission-btn');
|
||||||
const cameraDeviceSelection = document.getElementById('camera-device-selection');
|
const cameraDeviceSelection = document.getElementById('camera-device-selection');
|
||||||
const sceneCameraDeviceInput = document.getElementById('scene-camera-device-input');
|
const sceneCameraDeviceInput = document.getElementById('scene-camera-device-input');
|
||||||
|
const cameraErrorMessage = document.getElementById('camera-error-message');
|
||||||
|
|
||||||
// Data Store
|
// Data Store
|
||||||
let scenes = [];
|
let scenes = [];
|
||||||
@ -35,6 +36,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
let activeProgramSceneId = null;
|
let activeProgramSceneId = null;
|
||||||
let sceneIdCounter = 0;
|
let sceneIdCounter = 0;
|
||||||
let activeStreams = {}; // To hold references to active MediaStream objects
|
let activeStreams = {}; // To hold references to active MediaStream objects
|
||||||
|
let cameraPermissionGranted = false;
|
||||||
|
|
||||||
// --- STREAM MANAGEMENT ---
|
// --- STREAM MANAGEMENT ---
|
||||||
function stopStream(panel) {
|
function stopStream(panel) {
|
||||||
@ -129,8 +131,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
video.src = scene.value;
|
video.src = scene.value;
|
||||||
} else { // camera
|
} else { // camera
|
||||||
try {
|
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
|
const constraints = scene.value
|
||||||
? { video: { deviceId: { exact: scene.value } } }
|
? { video: { deviceId: { exact: scene.value } } }
|
||||||
: { video: true };
|
: { video: true };
|
||||||
@ -181,55 +181,68 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- CAMERA & DEVICE MANAGEMENT ---
|
// --- CAMERA & DEVICE MANAGEMENT ---
|
||||||
async function populateCameraDevices() {
|
function showCameraUI() {
|
||||||
// First, check for a secure context (HTTPS), which is required for getUserMedia.
|
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) {
|
if (!window.isSecureContext) {
|
||||||
console.error("Camera access requires a secure context (HTTPS).");
|
cameraErrorMessage.textContent = 'Camera access is only available over a secure HTTPS connection.';
|
||||||
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>';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stream = null;
|
||||||
try {
|
try {
|
||||||
// Request permission first
|
// First, get a stream. This is necessary to trigger the permission prompt
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
// 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 devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
||||||
|
|
||||||
sceneCameraDeviceInput.innerHTML = '';
|
sceneCameraDeviceInput.innerHTML = ''; // Clear previous options
|
||||||
if (videoDevices.length === 0) {
|
if (videoDevices.length === 0) {
|
||||||
// This case is unlikely if getUserMedia succeeded, but good to have.
|
cameraErrorMessage.textContent = 'No camera devices were found.';
|
||||||
cameraPermissionPrompt.innerHTML = '<p class="text-warning">No camera devices were found.</p>';
|
return; // Exit if no cameras
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate the dropdown
|
||||||
videoDevices.forEach(device => {
|
videoDevices.forEach(device => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = device.deviceId;
|
option.value = device.deviceId;
|
||||||
|
// Use the device label if available, otherwise a generic name
|
||||||
option.textContent = device.label || `Camera ${sceneCameraDeviceInput.length + 1}`;
|
option.textContent = device.label || `Camera ${sceneCameraDeviceInput.length + 1}`;
|
||||||
sceneCameraDeviceInput.appendChild(option);
|
sceneCameraDeviceInput.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Switch UI
|
// We have successfully populated the list, so update the UI
|
||||||
cameraPermissionPrompt.style.display = 'none';
|
cameraPermissionGranted = true;
|
||||||
cameraDeviceSelection.style.display = 'block';
|
showCameraUI();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Could not get camera permissions or list devices:", err);
|
console.error("Could not get camera permissions:", err);
|
||||||
let errorMessage = 'An unexpected error occurred while trying to access the camera.';
|
let msg = 'An unexpected error occurred.';
|
||||||
|
|
||||||
if (err.name === 'NotAllowedError') {
|
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') {
|
} 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') {
|
} 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);
|
stopStream(programPanel);
|
||||||
|
|
||||||
// Clone the content and style
|
|
||||||
programPanel.innerHTML = previewPanel.innerHTML;
|
programPanel.innerHTML = previewPanel.innerHTML;
|
||||||
programPanel.style.backgroundColor = previewPanel.style.backgroundColor;
|
programPanel.style.backgroundColor = previewPanel.style.backgroundColor;
|
||||||
programPanel.style.backgroundImage = previewPanel.style.backgroundImage;
|
programPanel.style.backgroundImage = previewPanel.style.backgroundImage;
|
||||||
|
|
||||||
const previewVideo = previewPanel.querySelector('video');
|
if (activeStreams[previewPanel.id]) {
|
||||||
if (previewVideo && previewVideo.srcObject) { // It's a stream
|
|
||||||
// Move the stream from preview to program
|
|
||||||
activeStreams[programPanel.id] = activeStreams[previewPanel.id];
|
activeStreams[programPanel.id] = activeStreams[previewPanel.id];
|
||||||
delete activeStreams[previewPanel.id];
|
delete activeStreams[previewPanel.id];
|
||||||
}
|
}
|
||||||
@ -272,7 +282,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
programPanel.querySelector('.panel-label').textContent = 'PROGRAM (ON AIR)';
|
programPanel.querySelector('.panel-label').textContent = 'PROGRAM (ON AIR)';
|
||||||
const programVideo = programPanel.querySelector('video');
|
const programVideo = programPanel.querySelector('video');
|
||||||
if(programVideo) {
|
if(programVideo) {
|
||||||
programVideo.muted = false; // Unmute in program
|
programVideo.muted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPreview();
|
clearPreview();
|
||||||
@ -281,7 +291,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
function showModal() {
|
function showModal() {
|
||||||
modal.style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
sceneNameInput.focus();
|
sceneNameInput.focus();
|
||||||
// Reset and update visibility on open
|
|
||||||
sceneTypeSelect.dispatchEvent(new Event('change'));
|
sceneTypeSelect.dispatchEvent(new Event('change'));
|
||||||
}
|
}
|
||||||
function hideModal() { modal.style.display = 'none'; }
|
function hideModal() { modal.style.display = 'none'; }
|
||||||
@ -298,18 +307,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
cameraSettings.style.display = type === 'camera' ? 'block' : 'none';
|
cameraSettings.style.display = type === 'camera' ? 'block' : 'none';
|
||||||
|
|
||||||
if (type === 'camera') {
|
if (type === 'camera') {
|
||||||
// Reset to initial state
|
showCameraUI();
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
grantCameraPermissionBtn.addEventListener('click', populateCameraDevices);
|
grantCameraPermissionBtn.addEventListener('click', requestCameraPermission);
|
||||||
|
|
||||||
async function saveScene() {
|
async function saveScene() {
|
||||||
const name = sceneNameInput.value.trim();
|
const name = sceneNameInput.value.trim();
|
||||||
@ -362,13 +364,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const newScene = addScene(name, type, value);
|
const newScene = addScene(name, type, value);
|
||||||
updatePreview(newScene.id);
|
updatePreview(newScene.id);
|
||||||
|
|
||||||
// Reset form and hide modal
|
|
||||||
sceneNameInput.value = '';
|
sceneNameInput.value = '';
|
||||||
sceneColorInput.value = '#1e90ff';
|
sceneColorInput.value = '#1e90ff';
|
||||||
sceneImageFileInput.value = '';
|
sceneImageFileInput.value = '';
|
||||||
sceneVideoFileInput.value = '';
|
sceneVideoFileInput.value = '';
|
||||||
// Don't clear camera list, just reset selection
|
|
||||||
sceneTypeSelect.value = 'color';
|
sceneTypeSelect.value = 'color';
|
||||||
|
|
||||||
colorSettings.style.display = 'block';
|
colorSettings.style.display = 'block';
|
||||||
imageSettings.style.display = 'none';
|
imageSettings.style.display = 'none';
|
||||||
videoSettings.style.display = 'none';
|
videoSettings.style.display = 'none';
|
||||||
@ -387,9 +388,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
// --- INITIALIZATION ---
|
// --- INITIALIZATION ---
|
||||||
function initialize() {
|
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('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('Promo Video', 'video', 'https://assets.mixkit.co/videos/preview/mixkit-spinning-around-the-earth-29351-large.mp4');
|
||||||
addScene('Screen Share', 'color', '#006633');
|
addScene('Screen Share', 'color', '#006633');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user