Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced YouTube Watch Time Monitor</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .progress-bar { | |
| height: 8px; | |
| border-radius: 4px; | |
| background-color: #e5e7eb; | |
| overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| border-radius: 4px; | |
| background: linear-gradient(90deg, #3b82f6, #8b5cf6); | |
| transition: width 0.3s ease; | |
| } | |
| .glow { | |
| box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); | |
| } | |
| .device-card { | |
| transition: all 0.3s ease; | |
| } | |
| .device-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |
| } | |
| .user-agent-badge { | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.75rem; | |
| } | |
| .analytics-chart { | |
| height: 300px; | |
| background-color: #f8fafc; | |
| border-radius: 0.5rem; | |
| position: relative; | |
| } | |
| .chart-line { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 2px; | |
| background-color: #e2e8f0; | |
| } | |
| .chart-bar { | |
| position: absolute; | |
| bottom: 0; | |
| width: 30px; | |
| background: linear-gradient(to top, #3b82f6, #8b5cf6); | |
| border-radius: 4px 4px 0 0; | |
| transition: height 0.5s ease; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 text-gray-800"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="mb-8"> | |
| <h1 class="text-3xl font-bold text-gray-900 mb-2">YouTube Watch Time Monitor</h1> | |
| <p class="text-gray-600">Advanced analytics dashboard for tracking watch hours with detailed session data</p> | |
| </header> | |
| <!-- Main Dashboard --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> | |
| <!-- Video Player Section --> | |
| <div class="lg:col-span-2 bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold">Video Monitoring</h2> | |
| </div> | |
| <div class="p-4"> | |
| <div class="aspect-w-16 aspect-h-9 bg-black rounded-lg overflow-hidden mb-4"> | |
| <iframe id="yt-player" class="w-full h-96" src="https://www.youtube.com/embed/dQw4w9WgXcQ?enablejsapi=1&autoplay=1&mute=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> | |
| </div> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div> | |
| <h3 class="font-medium">Never Gonna Give You Up</h3> | |
| <p class="text-sm text-gray-500">Rick Astley • 1.3B views</p> | |
| </div> | |
| <button id="change-video" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Change Video</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stats Overview --> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold">Watch Time Analytics</h2> | |
| </div> | |
| <div class="p-4 space-y-6"> | |
| <div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm font-medium">Total Watch Hours</span> | |
| <span id="total-hours" class="text-sm font-bold">0.00</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="hours-progress" class="progress-fill" style="width: 0%"></div> | |
| </div> | |
| <p class="text-xs text-gray-500 mt-1">Target: 4,000 hours (for monetization)</p> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div class="bg-gray-50 p-3 rounded-lg"> | |
| <p class="text-xs text-gray-500 mb-1">Today</p> | |
| <p id="today-hours" class="text-xl font-bold">0.00</p> | |
| <p class="text-xs text-green-600">+0% from yesterday</p> | |
| </div> | |
| <div class="bg-gray-50 p-3 rounded-lg"> | |
| <p class="text-xs text-gray-500 mb-1">This Week</p> | |
| <p id="week-hours" class="text-xl font-bold">0.00</p> | |
| <p class="text-xs text-green-600">+0% from last week</p> | |
| </div> | |
| </div> | |
| <div> | |
| <h4 class="text-sm font-medium mb-2">Traffic Sources</h4> | |
| <div class="space-y-2"> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 bg-blue-500 rounded-full mr-2"></div> | |
| <span class="text-sm flex-1">Google Ads</span> | |
| <span id="google-ads" class="text-sm font-medium">0%</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 bg-purple-500 rounded-full mr-2"></div> | |
| <span class="text-sm flex-1">Organic Search</span> | |
| <span id="organic-search" class="text-sm font-medium">0%</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 bg-green-500 rounded-full mr-2"></div> | |
| <span class="text-sm flex-1">Direct</span> | |
| <span id="direct" class="text-sm font-medium">0%</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 bg-yellow-500 rounded-full mr-2"></div> | |
| <span class="text-sm flex-1">Social Media</span> | |
| <span id="social" class="text-sm font-medium">0%</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Detailed Analytics --> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> | |
| <!-- Watch Time Chart --> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold">Watch Time Distribution</h2> | |
| </div> | |
| <div class="p-4"> | |
| <div class="analytics-chart mb-4" id="watch-time-chart"> | |
| <div class="chart-line"></div> | |
| <!-- Bars will be added dynamically --> | |
| </div> | |
| <div class="flex justify-between text-xs text-gray-500"> | |
| <span>Mon</span> | |
| <span>Tue</span> | |
| <span>Wed</span> | |
| <span>Thu</span> | |
| <span>Fri</span> | |
| <span>Sat</span> | |
| <span>Sun</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Device Distribution --> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold">Device Distribution</h2> | |
| </div> | |
| <div class="p-4"> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div class="device-card bg-gray-50 p-4 rounded-lg text-center"> | |
| <i class="fas fa-mobile-alt text-3xl text-blue-500 mb-2"></i> | |
| <p class="font-medium">Mobile</p> | |
| <p id="mobile-percent" class="text-2xl font-bold">0%</p> | |
| </div> | |
| <div class="device-card bg-gray-50 p-4 rounded-lg text-center"> | |
| <i class="fas fa-desktop text-3xl text-purple-500 mb-2"></i> | |
| <p class="font-medium">Desktop</p> | |
| <p id="desktop-percent" class="text-2xl font-bold">0%</p> | |
| </div> | |
| <div class="device-card bg-gray-50 p-4 rounded-lg text-center"> | |
| <i class="fas fa-tablet-alt text-3xl text-green-500 mb-2"></i> | |
| <p class="font-medium">Tablet</p> | |
| <p id="tablet-percent" class="text-2xl font-bold">0%</p> | |
| </div> | |
| <div class="device-card bg-gray-50 p-4 rounded-lg text-center"> | |
| <i class="fas fa-tv text-3xl text-yellow-500 mb-2"></i> | |
| <p class="font-medium">TV</p> | |
| <p id="tv-percent" class="text-2xl font-bold">0%</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Active Sessions --> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8"> | |
| <div class="p-4 border-b border-gray-200 flex justify-between items-center"> | |
| <h2 class="text-xl font-semibold">Active Viewing Sessions</h2> | |
| <div class="flex items-center"> | |
| <span class="text-sm text-gray-500 mr-2">Simulation Speed:</span> | |
| <select id="simulation-speed" class="text-sm border rounded px-2 py-1"> | |
| <option value="1">1x</option> | |
| <option value="2">2x</option> | |
| <option value="5">5x</option> | |
| <option value="10">10x</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="p-4"> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Device</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Location</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">User Agent</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th> | |
| </tr> | |
| </thead> | |
| <tbody id="sessions-table" class="bg-white divide-y divide-gray-200"> | |
| <!-- Sessions will be added dynamically --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="mt-4 flex justify-center"> | |
| <button id="add-session" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center"> | |
| <i class="fas fa-plus mr-2"></i> Add Simulated Session | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Real-time Stats --> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold">Real-time Statistics</h2> | |
| </div> | |
| <div class="p-4 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-blue-50 p-4 rounded-lg flex items-center"> | |
| <div class="bg-blue-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-users text-blue-600 text-xl"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-gray-600">Current Viewers</p> | |
| <p id="current-viewers" class="text-2xl font-bold">0</p> | |
| </div> | |
| </div> | |
| <div class="bg-purple-50 p-4 rounded-lg flex items-center"> | |
| <div class="bg-purple-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-clock text-purple-600 text-xl"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-gray-600">Avg. Watch Time</p> | |
| <p id="avg-watch-time" class="text-2xl font-bold">0:00</p> | |
| </div> | |
| </div> | |
| <div class="bg-green-50 p-4 rounded-lg flex items-center"> | |
| <div class="bg-green-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-percentage text-green-600 text-xl"></i> | |
| </div> | |
| <div> | |
| <p class="text-sm text-gray-600">Retention Rate</p> | |
| <p id="retention-rate" class="text-2xl font-bold">0%</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables | |
| let totalWatchHours = 0; | |
| let todayWatchHours = 0; | |
| let weekWatchHours = 0; | |
| let currentViewers = 0; | |
| let simulationSpeed = 1; | |
| let activeSessions = []; | |
| let watchTimeData = [0, 0, 0, 0, 0, 0, 0]; | |
| let deviceDistribution = { | |
| mobile: 0, | |
| desktop: 0, | |
| tablet: 0, | |
| tv: 0 | |
| }; | |
| let trafficSources = { | |
| googleAds: 0, | |
| organicSearch: 0, | |
| direct: 0, | |
| social: 0 | |
| }; | |
| let sessionIdCounter = 0; | |
| let videoPlayer; | |
| let videoDuration = 213; // Rick Astley video duration in seconds | |
| // Device and location data for simulation | |
| const devices = [ | |
| { type: 'mobile', name: 'iPhone 13', os: 'iOS 15' }, | |
| { type: 'mobile', name: 'Samsung Galaxy S22', os: 'Android 12' }, | |
| { type: 'desktop', name: 'Windows PC', os: 'Windows 11' }, | |
| { type: 'desktop', name: 'MacBook Pro', os: 'macOS Monterey' }, | |
| { type: 'tablet', name: 'iPad Pro', os: 'iPadOS 15' }, | |
| { type: 'tablet', name: 'Samsung Galaxy Tab S8', os: 'Android 12' }, | |
| { type: 'tv', name: 'Smart TV', os: 'Android TV' } | |
| ]; | |
| const locations = [ | |
| { country: 'United States', city: 'New York' }, | |
| { country: 'United Kingdom', city: 'London' }, | |
| { country: 'Canada', city: 'Toronto' }, | |
| { country: 'Australia', city: 'Sydney' }, | |
| { country: 'Germany', city: 'Berlin' }, | |
| { country: 'Japan', city: 'Tokyo' }, | |
| { country: 'Brazil', city: 'São Paulo' } | |
| ]; | |
| // User agents for different devices | |
| const userAgents = { | |
| mobile: [ | |
| 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1', | |
| 'Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Mobile Safari/537.36' | |
| ], | |
| desktop: [ | |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36', | |
| 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15' | |
| ], | |
| tablet: [ | |
| 'Mozilla/5.0 (iPad; CPU OS 15_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1', | |
| 'Mozilla/5.0 (Linux; Android 12; SM-X800) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Safari/537.36' | |
| ], | |
| tv: [ | |
| 'Mozilla/5.0 (Linux; Android 11; Mi TV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Safari/537.36' | |
| ] | |
| }; | |
| // Traffic sources | |
| const trafficSourcesList = [ | |
| { type: 'googleAds', name: 'Google Ads' }, | |
| { type: 'organicSearch', name: 'Organic Search' }, | |
| { type: 'direct', name: 'Direct' }, | |
| { type: 'social', name: 'Social Media' } | |
| ]; | |
| // Initialize the dashboard | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Load YouTube API | |
| const tag = document.createElement('script'); | |
| tag.src = "https://www.youtube.com/iframe_api"; | |
| const firstScriptTag = document.getElementsByTagName('script')[0]; | |
| firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); | |
| // Initialize chart | |
| updateWatchTimeChart(); | |
| updateDeviceDistribution(); | |
| updateTrafficSources(); | |
| // Set up event listeners | |
| document.getElementById('add-session').addEventListener('click', addSimulatedSession); | |
| document.getElementById('simulation-speed').addEventListener('change', function() { | |
| simulationSpeed = parseInt(this.value); | |
| }); | |
| document.getElementById('change-video').addEventListener('click', changeVideo); | |
| // Start with 5 simulated sessions | |
| for (let i = 0; i < 5; i++) { | |
| setTimeout(() => addSimulatedSession(), i * 2000); | |
| } | |
| // Update stats every second | |
| setInterval(updateStats, 1000); | |
| }); | |
| // YouTube API callback | |
| function onYouTubeIframeAPIReady() { | |
| videoPlayer = new YT.Player('yt-player', { | |
| events: { | |
| 'onReady': onPlayerReady, | |
| 'onStateChange': onPlayerStateChange | |
| } | |
| }); | |
| } | |
| function onPlayerReady(event) { | |
| // Player is ready | |
| } | |
| function onPlayerStateChange(event) { | |
| // Handle player state changes if needed | |
| } | |
| function changeVideo() { | |
| const videos = [ | |
| 'YOUR_VIDEO_ID_1', // Replace with your first video ID | |
| 'YOUR_VIDEO_ID_2', // Replace with your second video ID | |
| 'YOUR_VIDEO_ID_3', // Replace with your third video ID | |
| 'YOUR_VIDEO_ID_4', // Replace with your fourth video ID | |
| 'YOUR_VIDEO_ID_5' // Replace with your fifth video ID | |
| ]; | |
| const randomVideo = videos[Math.floor(Math.random() * videos.length)]; | |
| videoPlayer.loadVideoById(randomVideo); | |
| // Update video info | |
| document.querySelector('#yt-player + div h3').textContent = getVideoTitle(randomVideo); | |
| document.querySelector('#yt-player + div p').textContent = getVideoInfo(randomVideo); | |
| } | |
| function getVideoTitle(videoId) { | |
| const titles = { | |
| 'YOUR_VIDEO_ID_1': 'Your Video 1 Title', | |
| 'YOUR_VIDEO_ID_2': 'Your Video 2 Title', | |
| 'YOUR_VIDEO_ID_3': 'Your Video 3 Title', | |
| 'YOUR_VIDEO_ID_4': 'Your Video 4 Title', | |
| 'YOUR_VIDEO_ID_5': 'Your Video 5 Title' | |
| }; | |
| return titles[videoId] || 'Unknown Video'; | |
| } | |
| function getVideoInfo(videoId) { | |
| const infos = { | |
| 'YOUR_VIDEO_ID_1': 'Your Channel • 0 views', | |
| 'YOUR_VIDEO_ID_2': 'Your Channel • 0 views', | |
| 'YOUR_VIDEO_ID_3': 'Your Channel • 0 views', | |
| 'YOUR_VIDEO_ID_4': 'Your Channel • 0 views', | |
| 'YOUR_VIDEO_ID_5': 'Your Channel • 0 views' | |
| }; | |
| return infos[videoId] || 'Unknown Artist • 0 views'; | |
| } | |
| // Add a simulated viewing session | |
| function addSimulatedSession() { | |
| sessionIdCounter++; | |
| // Randomly select device, location, and traffic source | |
| const device = devices[Math.floor(Math.random() * devices.length)]; | |
| const location = locations[Math.floor(Math.random() * locations.length)]; | |
| const trafficSource = trafficSourcesList[Math.floor(Math.random() * trafficSourcesList.length)]; | |
| // Get appropriate user agent | |
| const userAgent = userAgents[device.type][Math.floor(Math.random() * userAgents[device.type].length)]; | |
| // Random watch duration between 30 seconds and full video length | |
| const duration = Math.floor(Math.random() * (videoDuration - 30)) + 30; | |
| // Create session object | |
| const session = { | |
| id: sessionIdCounter, | |
| device: device, | |
| location: location, | |
| userAgent: userAgent, | |
| trafficSource: trafficSource, | |
| duration: duration, | |
| startTime: new Date(), | |
| watched: 0, | |
| active: true | |
| }; | |
| activeSessions.push(session); | |
| currentViewers++; | |
| // Update device distribution | |
| deviceDistribution[device.type]++; | |
| updateDeviceDistribution(); | |
| // Update traffic sources | |
| trafficSources[trafficSource.type]++; | |
| updateTrafficSources(); | |
| // Add to sessions table | |
| addSessionToTable(session); | |
| // Schedule session completion | |
| setTimeout(() => completeSession(session), (duration * 1000) / simulationSpeed); | |
| } | |
| // Add session to the table | |
| function addSessionToTable(session) { | |
| const tableBody = document.getElementById('sessions-table'); | |
| const row = document.createElement('tr'); | |
| row.id = `session-${session.id}`; | |
| row.className = 'p-2'; | |
| const timeCell = document.createElement('td'); | |
| timeCell.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-500'; | |
| timeCell.textContent = session.startTime.toLocaleTimeString(); | |
| const deviceCell = document.createElement('td'); | |
| deviceCell.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-900'; | |
| const deviceDiv = document.createElement('div'); | |
| deviceDiv.className = 'flex items-center'; | |
| const deviceIcon = document.createElement('i'); | |
| deviceIcon.className = device.type === 'mobile' ? 'fas fa-mobile-alt mr-2 text-blue-500' : | |
| device.type === 'desktop' ? 'fas fa-desktop mr-2 text-purple-500' : | |
| device.type === 'tablet' ? 'fas fa-tablet-alt mr-2 text-green-500' : | |
| 'fas fa-tv mr-2 text-yellow-500'; | |
| const deviceText = document.createElement('div'); | |
| deviceText.innerHTML = `<div class="font-medium">${session.device.name}</div><div class="text-xs text-gray-500">${session.device.os}</div>`; | |
| deviceDiv.appendChild(deviceIcon); | |
| deviceDiv.appendChild(deviceText); | |
| deviceCell.appendChild(deviceDiv); | |
| const locationCell = document.createElement('td'); | |
| locationCell.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-900'; | |
| locationCell.innerHTML = `<div class="font-medium">${session.location.city}</div><div class="text-xs text-gray-500">${session.location.country}</div>`; | |
| const userAgentCell = document.createElement('td'); | |
| userAgentCell.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-900'; | |
| const userAgentBadge = document.createElement('span'); | |
| userAgentBadge.className = 'user-agent-badge inline-block bg-gray-100 rounded px-2 py-1 text-gray-800 truncate max-w-xs'; | |
| userAgentBadge.textContent = session.userAgent; | |
| userAgentBadge.title = session.userAgent; | |
| userAgentCell.appendChild(userAgentBadge); | |
| const durationCell = document.createElement('td'); | |
| durationCell.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-900 font-medium'; | |
| durationCell.id = `duration-${session.id}`; | |
| durationCell.textContent = '0:00'; | |
| row.appendChild(timeCell); | |
| row.appendChild(deviceCell); | |
| row.appendChild(locationCell); | |
| row.appendChild(userAgentCell); | |
| row.appendChild(durationCell); | |
| tableBody.prepend(row); | |
| } | |
| // Complete a session | |
| function completeSession(session) { | |
| session.active = false; | |
| currentViewers--; | |
| // Calculate watch time in hours | |
| const watchHours = session.duration / 3600; | |
| // Update watch time stats | |
| totalWatchHours += watchHours; | |
| todayWatchHours += watchHours; | |
| weekWatchHours += watchHours; | |
| // Update watch time data for the current day of week | |
| const dayOfWeek = new Date().getDay(); | |
| watchTimeData[dayOfWeek] += watchHours; | |
| updateWatchTimeChart(); | |
| // Remove session from active sessions | |
| activeSessions = activeSessions.filter(s => s.id !== session.id); | |
| // Update UI | |
| updateStats(); | |
| } | |
| // Update all stats | |
| function updateStats() { | |
| // Update watch time | |
| document.getElementById('total-hours').textContent = totalWatchHours.toFixed(2); | |
| document.getElementById('today-hours').textContent = todayWatchHours.toFixed(2); | |
| document.getElementById('week-hours').textContent = weekWatchHours.toFixed(2); | |
| // Update progress bar | |
| const progressPercent = Math.min((totalWatchHours / 4000) * 100, 100); | |
| document.getElementById('hours-progress').style.width = `${progressPercent}%`; | |
| // Update current viewers | |
| document.getElementById('current-viewers').textContent = currentViewers; | |
| // Update average watch time | |
| const avgWatchTime = activeSessions.length > 0 ? | |
| activeSessions.reduce((sum, session) => sum + session.duration, 0) / activeSessions.length : 0; | |
| document.getElementById('avg-watch-time').textContent = formatTime(avgWatchTime); | |
| // Update retention rate (simplified) | |
| const retentionRate = Math.min(Math.floor((avgWatchTime / videoDuration) * 100), 100); | |
| document.getElementById('retention-rate').textContent = `${retentionRate}%`; | |
| // Update session durations | |
| activeSessions.forEach(session => { | |
| const elapsed = Math.min(Math.floor((new Date() - session.startTime) / 1000 * simulationSpeed), session.duration); | |
| document.getElementById(`duration-${session.id}`).textContent = formatTime(elapsed); | |
| }); | |
| } | |
| // Update watch time chart | |
| function updateWatchTimeChart() { | |
| const chart = document.getElementById('watch-time-chart'); | |
| // Clear existing bars | |
| const existingBars = chart.querySelectorAll('.chart-bar'); | |
| existingBars.forEach(bar => bar.remove()); | |
| // Find max value for scaling | |
| const maxValue = Math.max(...watchTimeData, 1); | |
| // Add new bars | |
| for (let i = 0; i < watchTimeData.length; i++) { | |
| const bar = document.createElement('div'); | |
| bar.className = 'chart-bar'; | |
| bar.style.left = `${10 + i * 14}%`; | |
| bar.style.height = `${(watchTimeData[i] / maxValue) * 100}%`; | |
| chart.appendChild(bar); | |
| } | |
| } | |
| // Update device distribution | |
| function updateDeviceDistribution() { | |
| const totalDevices = Object.values(deviceDistribution).reduce((sum, val) => sum + val, 1); | |
| const mobilePercent = Math.round((deviceDistribution.mobile / totalDevices) * 100); | |
| const desktopPercent = Math.round((deviceDistribution.desktop / totalDevices) * 100); | |
| const tabletPercent = Math.round((deviceDistribution.tablet / totalDevices) * 100); | |
| const tvPercent = Math.round((deviceDistribution.tv / totalDevices) * 100); | |
| document.getElementById('mobile-percent').textContent = `${mobilePercent}%`; | |
| document.getElementById('desktop-percent').textContent = `${desktopPercent}%`; | |
| document.getElementById('tablet-percent').textContent = `${tabletPercent}%`; | |
| document.getElementById('tv-percent').textContent = `${tvPercent}%`; | |
| } | |
| // Update traffic sources | |
| function updateTrafficSources() { | |
| const totalSources = Object.values(trafficSources).reduce((sum, val) => sum + val, 1); | |
| const googleAdsPercent = Math.round((trafficSources.googleAds / totalSources) * 100); | |
| const organicSearchPercent = Math.round((trafficSources.organicSearch / totalSources) * 100); | |
| const directPercent = Math.round((trafficSources.direct / totalSources) * 100); | |
| const socialPercent = Math.round((trafficSources.social / totalSources) * 100); | |
| document.getElementById('google-ads').textContent = `${googleAdsPercent}%`; | |
| document.getElementById('organic-search').textContent = `${organicSearchPercent}%`; | |
| document.getElementById('direct').textContent = `${directPercent}%`; | |
| document.getElementById('social').textContent = `${socialPercent}%`; | |
| } | |
| // Format time (seconds to MM:SS) | |
| function formatTime(seconds) { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = Math.floor(seconds % 60); | |
| return `${mins}:${secs < 10 ? '0' : ''}${secs}`; | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Muhammadumar786/yt" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |