403Webshell
Server IP : 127.0.0.1  /  Your IP : 216.73.216.48
Web Server : Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12
System : Windows NT DESKTOP-3H4FHQJ 10.0 build 19045 (Windows 10) AMD64
User : win 10 ( 0)
PHP Version : 8.2.12
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : OFF |  Perl : OFF |  Python : OFF |  Sudo : OFF |  Pkexec : OFF
Directory :  D:/xampp/htdocs-coblaa/pureFaith/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : D:/xampp/htdocs-coblaa/pureFaith/liveMe2.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multi-User Live Stream Signaling (Passcode Role)</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
        body {
            font-family: 'Inter', sans-serif;
            background-color: #0F172A; /* Slate 900 */
        }
        .stream-container {
            width: 100%;
            max-width: 900px;
        }
        .aspect-16-9 { aspect-ratio: 16 / 9; }
        .aspect-4-3 { aspect-ratio: 4 / 3; }
        .aspect-1-1 { aspect-ratio: 1 / 1; }
        #video-feed {
            object-fit: cover;
            width: 100%;
            height: 100%;
            background-color: #1E293B; /* Slate 800 */
        }
        .recording-pulse {
            animation: pulse-red 1s infinite;
        }
        @keyframes pulse-red {
            0%, 100% { background-color: rgba(239, 68, 68, 0.9); }
            50% { background-color: rgba(239, 68, 68, 0.4); }
        }
    </style>
</head>
<body class="min-h-screen flex flex-col items-center p-4">

    <header class="text-center mb-6 w-full max-w-2xl">
        <h1 class="text-3xl font-bold text-white mb-1">Live Stream Signaling with Role Gate</h1>
        <p id="user-info" class="text-sm text-gray-400">Loading User ID...</p>
        <p id="role-status" class="text-base font-semibold mt-2 p-2 rounded-lg bg-indigo-900 text-indigo-300">
            Role: Viewer (Default)
        </p>
        <p id="streamer-status" class="text-base font-semibold mt-2 p-2 rounded-lg bg-gray-700 text-gray-300">
            Stream Status: Offline
        </p>
    </header>

    <!-- Main Stream Container -->
    <div id="stream-container" class="stream-container aspect-16-9 rounded-xl shadow-2xl overflow-hidden relative border-4 border-indigo-500 mb-8">
        <video id="video-feed" autoplay playsinline class="rounded-lg"></video>
        
        <!-- Status Overlay for loading, errors, and viewing other streams -->
        <div id="status-overlay" class="absolute inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center p-6 transition-opacity duration-300">
            <div class="text-center space-y-4">
                <svg id="status-icon" class="w-12 h-12 mx-auto text-indigo-400 animate-spin" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
                    <path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
                </svg>
                <p id="status-text" class="text-lg font-semibold text-white">Initializing application...</p>
                <div id="error-details" class="text-sm text-red-400 hidden p-2 bg-gray-700 rounded-lg"></div>
            </div>
        </div>
    </div>

    <!-- Controls Container -->
    <div class="w-full max-w-xl space-y-4">
        
        <!-- Admin Access Input (Visible until successfully logged in as Admin) -->
        <div id="admin-login-panel" class="p-4 bg-gray-800 rounded-xl shadow-lg border border-yellow-500">
            <p class="text-sm font-semibold text-yellow-300 mb-3 text-center">Admin ID Access: (Passcode is **45544554**)</p>
            <div class="flex space-x-2">
                <input type="password" id="admin-id-input" placeholder="Enter Admin Passcode" class="flex-grow p-3 rounded-lg bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-yellow-500">
                <button id="admin-check-btn" class="p-3 bg-yellow-600 text-white rounded-lg font-semibold hover:bg-yellow-700 transition-colors shadow-md">
                    Check ID
                </button>
            </div>
            <p id="login-message" class="text-center mt-2 text-sm hidden"></p>
        </div>
        
        <!-- Viewer Controls (Visible only when watching a live stream) -->
        <div id="viewer-controls" class="p-4 bg-gray-700 rounded-xl shadow-lg hidden">
            <p class="text-sm font-semibold text-gray-300 mb-3 text-center">Viewing Controls</p>
            <div class="flex items-center space-x-4">
                <button id="mute-btn" class="p-3 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors shadow-md flex-shrink-0">
                    <!-- Mute Icon -->
                    <svg id="mute-icon" class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zm-5 8h4v2H5V10zM11 6h4v2h-4V6zm0 4h4v2h-4v-2zm-6 4h4v2H5v-2z"></path></svg>
                </button>
                <input type="range" id="volume-slider" min="0" max="100" value="100" class="w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer range-lg">
            </div>
        </div>

        <!-- Admin Controls (Visible only to the designated Admin) -->
        <div id="admin-controls" class="space-y-4 hidden">
            <!-- Live Stream Control Buttons (Go Live/Stop) -->
            <div class="p-3 bg-gray-800 rounded-xl shadow-lg">
                <p class="text-sm font-semibold text-gray-300 mb-3 text-center">Admin Live Controls:</p>
                <div class="flex flex-col md:flex-row space-y-3 md:space-y-0 md:space-x-4">
                    <button id="start-btn" class="w-full bg-indigo-600 text-white p-3 rounded-lg font-semibold hover:bg-indigo-700 transition-colors duration-200 shadow-md disabled:opacity-50" disabled>
                        Go Live
                    </button>
                    <button id="switch-camera-btn" class="w-full md:w-1/2 bg-gray-600 text-white p-3 rounded-lg font-semibold hover:bg-gray-700 transition-colors duration-200 shadow-md hidden disabled:opacity-50" disabled>
                        Switch Camera
                    </button>
                    <button id="stop-btn" class="w-full md:w-1/2 bg-red-600 text-white p-3 rounded-lg font-semibold hover:bg-red-700 transition-colors duration-200 shadow-md hidden disabled:opacity-50" disabled>
                        Stop Stream
                    </button>
                </div>
            </div>

            <!-- Recording Controls -->
            <div class="p-3 bg-gray-800 rounded-xl shadow-lg" id="recording-panel">
                <p class="text-sm font-semibold text-gray-300 mb-3 text-center">Recording (Only when Live):</p>
                <div class="flex justify-center space-x-3">
                    <button id="record-btn" class="p-2 w-1/2 rounded-lg bg-green-600 text-white text-sm font-medium hover:bg-green-700 transition-colors shadow-md disabled:opacity-50" disabled>
                        <span id="record-icon">
                            <svg class="w-5 h-5 inline-block mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>
                        </span>
                        <span id="record-text">Start Recording</span>
                    </button>
                    <button id="stop-record-btn" class="p-2 w-1/2 rounded-lg bg-red-800 text-white text-sm font-medium hover:bg-red-900 transition-colors shadow-md hidden disabled:opacity-50" disabled>
                        <svg class="w-5 h-5 inline-block mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9 9a1 1 0 011-1h1a1 1 0 110 2h-1a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
                        Stop & Download
                    </button>
                </div>
            </div>

            <!-- Aspect Ratio Controls -->
            <div class="p-3 bg-gray-800 rounded-xl shadow-lg">
                <p class="text-sm font-semibold text-gray-300 mb-3 text-center">Aspect Ratio (Requires Restart):</p>
                <div id="ratio-controls" class="flex justify-center space-x-3">
                    <button data-ratio="16/9" data-class="aspect-16-9" class="ratio-btn p-2 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 transition-colors shadow-md disabled:opacity-50">16:9</button>
                    <button data-ratio="4/3" data-class="aspect-4-3" class="ratio-btn p-2 rounded-lg bg-gray-700 text-white text-sm font-medium hover:bg-indigo-500 transition-colors shadow-md disabled:opacity-50">4:3</button>
                    <button data-ratio="1/1" data-class="aspect-1-1" class="ratio-btn p-2 rounded-lg bg-gray-700 text-white text-sm font-medium hover:bg-indigo-500 transition-colors shadow-md disabled:opacity-50">1:1</button>
                </div>
            </div>
        </div>
    </div>

    <script type="module">
        // Combined Imports for the module scope
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, setDoc, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        document.addEventListener('DOMContentLoaded', () => {
            const videoFeed = document.getElementById('video-feed');
            const streamContainer = document.getElementById('stream-container');
            const startBtn = document.getElementById('start-btn');
            const stopBtn = document.getElementById('stop-btn');
            const switchCameraBtn = document.getElementById('switch-camera-btn');
            const ratioButtons = document.querySelectorAll('.ratio-btn');
            const statusOverlay = document.getElementById('status-overlay');
            const statusText = document.getElementById('status-text');
            const statusIcon = document.getElementById('status-icon');
            const errorDetails = document.getElementById('error-details');
            const userInfoElement = document.getElementById('user-info');
            const roleStatusElement = document.getElementById('role-status');
            const streamerStatusElement = document.getElementById('streamer-status');
            
            // Role-specific control containers
            const adminControls = document.getElementById('admin-controls');
            const viewerControls = document.getElementById('viewer-controls');

            // Admin Login Elements
            const adminLoginPanel = document.getElementById('admin-login-panel');
            const adminIdInput = document.getElementById('admin-id-input');
            const adminCheckBtn = document.getElementById('admin-check-btn');
            const loginMessage = document.getElementById('login-message');

            // Admin Recording elements
            const recordBtn = document.getElementById('record-btn');
            const stopRecordBtn = document.getElementById('stop-record-btn');
            const recordIcon = document.getElementById('record-icon');
            const recordText = document.getElementById('record-text');

            // Viewer elements
            const muteBtn = document.getElementById('mute-btn');
            const muteIcon = document.getElementById('mute-icon');
            const volumeSlider = document.getElementById('volume-slider');


            // --- Configuration & Global State ---

            /**
             * The hardcoded Admin Passcode/ID as requested by the user.
             */
            const HARDCODED_ADMIN_ID = "45544554"; 
            
            let app;
            let db;
            let auth;
            let currentUserId = null; // Firebase UID, used as the unique streamer identifier in Firestore
            let isLocallyElevatedAdmin = false; // Flag set if HARDCODED_ADMIN_ID is entered
            let currentStream = null;
            let isStreaming = false; 
            let isViewer = false; 
            let currentStreamerId = null; // The Firebase UID of the active streamer

            let currentFacingMode = 'user'; 
            let currentAspectRatio = 16 / 9;
            let currentAspectRatioClass = 'aspect-16-9';

            // Recording state
            let mediaRecorder = null;
            let recordedChunks = [];
            const mimeType = 'video/webm;codecs=vp8,opus'; 

            // --- Utility Functions ---

            function updateStatus(message, isError = false, details = '') {
                statusOverlay.classList.remove('hidden');
                statusText.textContent = message;
                // Only show spin icon if not an error and we aren't waiting for a stream to start
                statusIcon.classList.toggle('hidden', isError || isViewer); 
                statusIcon.classList.toggle('animate-spin', !isError && !isViewer);
                statusText.classList.toggle('text-white', !isError);
                statusText.classList.toggle('text-red-400', isError);

                if (isError && details) {
                    errorDetails.textContent = details;
                    errorDetails.classList.remove('hidden');
                } else {
                    errorDetails.classList.add('hidden');
                }
                
                if (!isViewer && videoFeed.srcObject) {
                    videoFeed.srcObject = null;
                }
            }

            function hideStatus() {
                statusOverlay.classList.add('opacity-0');
                setTimeout(() => {
                    statusOverlay.classList.add('hidden');
                    statusOverlay.classList.remove('opacity-0');
                }, 300); 
            }
            
            function renderRole() {
                // Set Admin/Viewer status based on the *entered* ID, not the Firebase UID
                if (isLocallyElevatedAdmin) {
                    roleStatusElement.textContent = "Role: Admin (Controls Unlocked)";
                    roleStatusElement.classList.add('bg-purple-800', 'text-purple-300');
                    roleStatusElement.classList.remove('bg-indigo-900');
                    adminLoginPanel.classList.add('hidden');
                } else {
                    roleStatusElement.textContent = "Role: Viewer (Default)";
                    roleStatusElement.classList.add('bg-indigo-900', 'text-indigo-300');
                    roleStatusElement.classList.remove('bg-purple-800');
                    adminLoginPanel.classList.remove('hidden');
                }
            }

            function setControlState(state) {
                // state: 'OFFLINE', 'STREAMING', 'VIEWING', 'LOADING'
                
                // 1. Manage Admin Controls Visibility - ONLY visible if locally elevated
                adminControls.classList.toggle('hidden', !isLocallyElevatedAdmin);

                // 2. Manage Viewer Controls Visibility
                viewerControls.classList.toggle('hidden', state !== 'VIEWING');

                // 3. Manage Admin Streamer Control buttons (Only relevant if Admin)
                if (isLocallyElevatedAdmin) {
                    const enableStreamerControls = state === 'STREAMING';
                    const enableOfflineControls = state === 'OFFLINE';
                    const disableAllControls = state === 'LOADING';

                    startBtn.disabled = disableAllControls || enableStreamerControls;
                    startBtn.classList.toggle('hidden', state === 'STREAMING');

                    stopBtn.disabled = !enableStreamerControls;
                    stopBtn.classList.toggle('hidden', state !== 'STREAMING');

                    switchCameraBtn.disabled = !enableStreamerControls;
                    switchCameraBtn.classList.toggle('hidden', state !== 'STREAMING');

                    recordBtn.disabled = !enableStreamerControls;
                    
                    ratioButtons.forEach(btn => btn.disabled = !enableOfflineControls);
                }
            }

            // --- Admin ID Check Logic ---

            function checkAdminId() {
                const enteredId = adminIdInput.value.trim();
                loginMessage.classList.remove('hidden', 'text-red-400', 'text-green-400');
                loginMessage.textContent = '';

                if (enteredId === HARDCODED_ADMIN_ID) {
                    isLocallyElevatedAdmin = true;
                    loginMessage.textContent = 'Admin Access Granted! Controls Unlocked.';
                    loginMessage.classList.add('text-green-400');
                    renderRole();
                    // Set initial state after successful login
                    setControlState(isStreaming ? 'STREAMING' : 'OFFLINE');
                    updateStatus(isStreaming ? 'Admin: You are currently streaming.' : 'Admin: Ready to Go Live.');
                } else {
                    isLocallyElevatedAdmin = false;
                    loginMessage.textContent = 'Access Denied. Incorrect ID.';
                    loginMessage.classList.add('text-red-400');
                    renderRole();
                    setControlState('OFFLINE');
                }
            }
            
            // --- Firebase Initialization and Auth ---

            async function initFirebase() {
                try {
                    const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
                    const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}');
                    const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

                    if (!firebaseConfig.apiKey) {
                         throw new Error("Firebase configuration is missing or invalid.");
                    }

                    app = initializeApp(firebaseConfig);
                    db = getFirestore(app);
                    auth = getAuth(app);
                    
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                    
                    onAuthStateChanged(auth, (user) => {
                        if (user) {
                            currentUserId = user.uid; // Store Firebase UID for data path use
                            userInfoElement.textContent = `Your session ID: ${currentUserId.substring(0, 8)}... (Used as Streamer ID in database)`;
                            
                            renderRole(); 
                            startStreamListener(appId);
                            setControlState('OFFLINE'); 
                            updateStatus('Enter Admin Passcode to enable streaming controls.');
                        } else {
                            currentUserId = null;
                            updateStatus('Authentication failed. Please refresh.', true);
                        }
                    });

                } catch (error) {
                    console.error("Firebase Initialization Error:", error);
                    updateStatus('Firebase Error.', true, `Could not initialize multi-user features: ${error.message}`);
                    setControlState('LOADING');
                }
            }

            // --- Firestore Stream Signaling ---

            function getStreamStatusDocRef(appId) {
                // Public data path: /artifacts/{appId}/public/data/live_stream_status/current_stream
                return doc(db, `artifacts/${appId}/public/data/live_stream_status`, 'current_stream');
            }

            function startStreamListener(appId) {
                const streamRef = getStreamStatusDocRef(appId);

                onSnapshot(streamRef, (docSnap) => {
                    const data = docSnap.exists() ? docSnap.data() : { streamerId: null };
                    currentStreamerId = data.streamerId;
                    
                    streamerStatusElement.classList.remove('bg-green-700', 'bg-red-700', 'bg-indigo-900', 'text-white', 'text-indigo-300');

                    if (currentStreamerId && currentStreamerId === currentUserId && isLocallyElevatedAdmin) {
                        // THIS USER IS THE STREAMER (Admin)
                        isStreaming = true;
                        isViewer = false;
                        streamerStatusElement.textContent = "Stream Status: YOU ARE LIVE!";
                        streamerStatusElement.classList.add('bg-green-700', 'text-white');
                        setControlState('STREAMING');
                        if (videoFeed.srcObject) hideStatus();

                    } else if (currentStreamerId) {
                        // THIS USER IS A VIEWER
                        isViewer = true;
                        isStreaming = false;
                        
                        // Stop local camera if it was running
                        if (currentStream) stopLocalStream();

                        streamerStatusElement.textContent = `Stream Status: LIVE by Streamer ${currentStreamerId.substring(0, 8)}...`;
                        streamerStatusElement.classList.add('bg-red-700', 'text-white');

                        // Update video area to show viewing status (simulate receiving stream)
                        updateStatus(`Live Stream is active. Watching ${currentStreamerId.substring(0, 8)}...`);
                        statusIcon.classList.add('hidden'); 
                        statusOverlay.style.backgroundImage = 'radial-gradient(circle, rgba(29, 78, 216, 0.5) 0%, rgba(15, 23, 42, 0.9) 100%)';

                        setControlState('VIEWING');

                    } else {
                        // NO ACTIVE STREAMER
                        isStreaming = false;
                        isViewer = false;
                        
                        // Stop local stream if it was running
                        if (currentStream) stopLocalStream();
                        
                        streamerStatusElement.textContent = "Stream Status: Offline";
                        streamerStatusElement.classList.add('bg-gray-700', 'text-gray-300');
                        statusOverlay.style.backgroundImage = 'none';

                        setControlState('OFFLINE');
                        
                        if (isLocallyElevatedAdmin) {
                             updateStatus('Admin: Ready to Go Live.');
                        } else {
                             updateStatus('Enter Admin Passcode to enable streaming controls.');
                        }
                    }
                });
            }

            async function goLive() {
                if (!isLocallyElevatedAdmin || isViewer) return;
                
                // 1. Start the local camera stream
                await startLocalStream();

                // 2. Announce the stream in Firestore using the Firebase UID as the identifier
                const streamRef = getStreamStatusDocRef(appId);
                await setDoc(streamRef, {
                    streamerId: currentUserId, // Use the Firebase UID here
                    isLive: true,
                });
            }

            async function stopLive() {
                if (!isLocallyElevatedAdmin || !isStreaming) return;

                // 1. Announce stream is off in Firestore
                const streamRef = getStreamStatusDocRef(appId);
                await setDoc(streamRef, {
                    streamerId: null, // Clear the streamer ID
                    isLive: false,
                });
                
                // 2. Stop local camera stream (the listener handles setting OFFLINE state)
                stopLocalStream();
            }

            // --- Local Stream Control Logic (Unchanged) ---
            
            async function startLocalStream() {
                if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                    updateStatus('Error: Media devices not supported.', true);
                    return;
                }

                updateStatus(`Requesting Camera at ${currentAspectRatio.toFixed(2)} ratio...`);
                setControlState('LOADING'); 

                if (currentStream) stopLocalStream();
                
                streamContainer.className = 'stream-container ' + currentAspectRatioClass + ' rounded-xl shadow-2xl overflow-hidden relative border-4 border-indigo-500 mb-8';

                try {
                    const stream = await navigator.mediaDevices.getUserMedia({ 
                        video: { 
                            facingMode: currentFacingMode, 
                            aspectRatio: currentAspectRatio,
                            width: { ideal: 1280 }, 
                            height: { ideal: 720 } 
                        }, 
                        audio: true 
                    });

                    currentStream = stream;
                    videoFeed.srcObject = stream;
                    videoFeed.muted = true; // Streamer must always be locally muted
                    await videoFeed.play(); 
                    
                    hideStatus();

                } catch (err) {
                    let errorMessage = 'An unknown error occurred while trying to access the camera.';
                    let errorHint = 'Ensure your device has a camera and try refreshing the page.';
                    
                    if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
                        errorMessage = 'Access Denied.';
                        errorHint = 'You must grant camera/microphone permission.';
                    } else if (err.name === 'NotFoundError' || err.name === 'OverconstrainedError') {
                        errorMessage = 'No Camera Found or Ratio Not Supported.';
                        errorHint = `No suitable camera device found.`;
                    } else if (err.name === 'SecurityError') {
                        errorMessage = 'Security Error: HTTPS Required.';
                        errorHint = 'This feature only works on secure connections (HTTPS) or localhost.';
                    }
                    
                    updateStatus(errorMessage, true, errorHint);
                    console.error('getUserMedia Error:', err);
                    setControlState('OFFLINE');
                }
            }

            function stopLocalStream() {
                if (mediaRecorder && mediaRecorder.state === 'recording') {
                    mediaRecorder.stop();
                }
                
                if (currentStream) {
                    currentStream.getTracks().forEach(track => track.stop());
                    currentStream = null;
                    videoFeed.srcObject = null;
                }
            }
            
            function switchCamera() {
                if (!isStreaming) return;
                currentFacingMode = (currentFacingMode === 'user' ? 'environment' : 'user');
                setControlState('LOADING');
                startLocalStream();
            }

            function handleRatioSwitch(event) {
                if (isStreaming) return; 
                
                const ratioStr = event.currentTarget.getAttribute('data-ratio');
                const ratioClass = event.currentTarget.getAttribute('data-class');
                
                currentAspectRatio = eval(ratioStr);
                currentAspectRatioClass = ratioClass;

                streamContainer.className = 'stream-container ' + currentAspectRatioClass + ' rounded-xl shadow-2xl overflow-hidden relative border-4 border-indigo-500 mb-8';
                updateRatioButtonStyles();
            }

            function updateRatioButtonStyles() {
                ratioButtons.forEach(btn => {
                    const btnRatio = btn.getAttribute('data-ratio');
                    const isActive = (eval(btnRatio) === currentAspectRatio);

                    if (isActive) {
                        btn.classList.add('bg-indigo-600', 'hover:bg-indigo-700');
                        btn.classList.remove('bg-gray-700', 'hover:bg-indigo-500');
                    } else {
                        btn.classList.add('bg-gray-700', 'hover:bg-indigo-500');
                        btn.classList.remove('bg-indigo-600', 'hover:bg-indigo-700');
                    }
                });
            }

            // --- Viewer Controls Logic (Unchanged) ---

            function toggleMute() {
                const isMuted = videoFeed.muted;
                videoFeed.muted = !isMuted;
                updateMuteButtonUI(!isMuted);
            }

            function updateMuteButtonUI(isMuted) {
                if (isMuted) {
                    muteIcon.innerHTML = `<path d="M10 2a8 8 0 100 16 8 8 0 000-16zm-5 8h4v2H5V10zM11 6h4v2h-4V6zm0 4h4v2h-4v-2zm-6 4h4v2H5v-2z"></path>`;
                    muteBtn.classList.remove('bg-green-600');
                    muteBtn.classList.add('bg-gray-500');
                } else {
                    muteIcon.innerHTML = `<path fill-rule="evenodd" d="M3 10a1 1 0 011-1h2.586l3.243-3.243A1 1 0 0111 6v8a1 1 0 01-1.171.97l-3.243-3.243H4a1 1 0 01-1-1v-2zM15.5 8.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z" clip-rule="evenodd"></path><path d="M16.146 6.854a.5.5 0 010 .708L14.707 9.5l1.439 1.938a.5.5 0 01-.708.708l-1.47-1.96V11a.5.5 0 01-1 0v-.586l-1.439 1.938a.5.5 0 01-.708-.708L13.293 9.5l-1.439-1.938a.5.5 0 01.708-.708L14 8.293V7a.5.5 0 011 0v1.586l1.146-1.532a.5.5 0 01.708 0z" fill-rule="evenodd"></path>`;
                    muteBtn.classList.add('bg-green-600');
                    muteBtn.classList.remove('bg-gray-500');
                }
            }

            function setVolume() {
                videoFeed.volume = volumeSlider.value / 100;
            }

            // --- Recording Logic (Only available to Admin) (Unchanged) ---

            function startRecording() {
                if (!currentStream || !isLocallyElevatedAdmin) return;
                
                recordedChunks = [];
                if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mimeType)) {
                     updateStatus('Error: Recording is not supported.', true, `Your browser does not support the required video/webm MIME type.`);
                     return;
                }
                
                try {
                    mediaRecorder = new MediaRecorder(currentStream, { mimeType: mimeType });

                    mediaRecorder.ondataavailable = (event) => {
                        if (event.data.size > 0) {
                            recordedChunks.push(event.data);
                        }
                    };

                    mediaRecorder.onstop = () => {
                        const blob = new Blob(recordedChunks, { type: mimeType });
                        const url = URL.createObjectURL(blob);
                        
                        const a = document.createElement('a');
                        a.style.display = 'none';
                        a.href = url;
                        a.download = `webcam-recording-${new Date().toISOString().replace(/[:.]/g, '-')}.webm`;
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        
                        // Reset UI
                        recordBtn.classList.remove('hidden');
                        recordBtn.disabled = false;
                        stopRecordBtn.classList.add('hidden');
                        stopRecordBtn.disabled = true;
                        recordText.textContent = 'Start Recording';
                        recordIcon.innerHTML = `<svg class="w-5 h-5 inline-block mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>`;
                        
                        if (isStreaming) hideStatus(); 
                    };

                    mediaRecorder.start(100); 

                    // Update UI state
                    recordBtn.classList.add('hidden');
                    recordBtn.disabled = true;
                    stopRecordBtn.classList.remove('hidden');
                    stopRecordBtn.disabled = false;
                    recordText.textContent = 'Recording...';
                    recordIcon.innerHTML = `<span class="recording-pulse inline-block w-4 h-4 rounded-full mr-2"></span>`;
                    
                    hideStatus();

                } catch (e) {
                    updateStatus('Recording failed.', true, `Error during MediaRecorder initialization: ${e.message}`);
                    console.error('MediaRecorder init error:', e);
                }
            }

            function stopRecording() {
                if (mediaRecorder && mediaRecorder.state === 'recording') {
                    mediaRecorder.stop();
                    stopRecordBtn.disabled = true; 
                    updateStatus('Processing recording for download...');
                }
            }

            // --- Event Listeners and Initial Setup ---
            
            // New Listener for Admin Login
            adminCheckBtn.addEventListener('click', checkAdminId);
            adminIdInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    checkAdminId();
                }
            });

            // Admin Controls
            startBtn.addEventListener('click', goLive);
            stopBtn.addEventListener('click', stopLive);
            switchCameraBtn.addEventListener('click', switchCamera);
            recordBtn.addEventListener('click', startRecording);
            stopRecordBtn.addEventListener('click', stopRecording);
            
            // Admin/Global Controls
            ratioButtons.forEach(button => {
                button.addEventListener('click', handleRatioSwitch);
            });
            
            // Viewer Controls
            muteBtn.addEventListener('click', toggleMute);
            volumeSlider.addEventListener('input', setVolume);

            // Initial setup
            updateRatioButtonStyles();
            setControlState('LOADING');
            updateMuteButtonUI(true); // Start muted
            
            initFirebase();
        });
    </script>
</body>
</html>

Youez - 2016 - github.com/yon3zu
LinuXploit