Spaces:
Running
Running
| // Multimedia Control System with Transformers.js | |
| class MultimediaController { | |
| constructor() { | |
| this.devices = []; | |
| this.playlist = []; | |
| this.currentVideo = null; | |
| this.isPlaying = false; | |
| this.progress = 0; | |
| this.volume = 50; | |
| this.aiModel = null; | |
| this.modelLoaded = false; | |
| this.initializeDevices(); | |
| this.initializePlaylist(); | |
| this.initializeAI(); | |
| this.startProgressUpdater(); | |
| } | |
| initializeDevices() { | |
| // Initialize simulated TV devices | |
| const deviceConfigs = [ | |
| { name: "TV - Sala Principal", location: "Sala", type: "4K Smart TV" }, | |
| { name: "TV - Barra", location: "Bar", type: "HD TV" }, | |
| { name: "TV - Terraza", location: "Terraza", type: "Outdoor TV" } | |
| ]; | |
| this.devices = deviceConfigs.map((config, index) => ({ | |
| id: index + 1, | |
| ...config, | |
| status: 'connected', | |
| isPlaying: false, | |
| currentContent: 'Esperando...', | |
| volume: 50, | |
| isMuted: false | |
| })); | |
| this.renderDevices(); | |
| this.renderIndividualControls(); | |
| } | |
| initializePlaylist() { | |
| this.playlist = [ | |
| { | |
| title: "Video Promocional - Restaurante", | |
| source: "YouTube", | |
| url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", | |
| duration: "3:45", | |
| thumbnail: "https://picsum.photos/seed/promo/60/40" | |
| }, | |
| { | |
| title: "Video Corporativo 2024", | |
| source: "Vimeo", | |
| url: "https://vimeo.com/123456789", | |
| duration: "2:30", | |
| thumbnail: "https://picsum.photos/seed/corp/60/40" | |
| }, | |
| { | |
| title: "Video Evento Especial", | |
| source: "Directo", | |
| url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", | |
| duration: "5:15", | |
| thumbnail: "https://picsum.photos/seed/event/60/40" | |
| } | |
| ]; | |
| this.renderPlaylist(); | |
| } | |
| async initializeAI() { | |
| try { | |
| // Initialize a text classification model for content analysis | |
| this.updateLoadingStatus('Cargando modelo de clasificación de texto...'); | |
| this.updateLoadingProgress(30); | |
| // Using a lightweight model for text classification | |
| this.aiModel = await pipeline('text-classification', 'Xenova/distilbert-base-uncased-finetuned-sst-2-english'); | |
| this.updateLoadingProgress(70); | |
| this.updateLoadingStatus('Modelo cargado exitosamente...'); | |
| this.modelLoaded = true; | |
| this.updateLoadingProgress(100); | |
| setTimeout(() => { | |
| document.getElementById('loadingScreen').classList.add('hidden'); | |
| this.updateAIModelStatus('Listo'); | |
| this.showStatus('✅ Sistema AI inicializado', 'success'); | |
| }, 1000); | |
| } catch (error) { | |
| console.error('Error loading AI model:', error); | |
| this.updateLoadingStatus('Error al cargar el modelo AI'); | |
| setTimeout(() => { | |
| document.getElementById('loadingScreen').classList.add('hidden'); | |
| this.updateAIModelStatus('Error'); | |
| this.showStatus('⚠️ AI no disponible (modo offline)', 'warning'); | |
| }, 2000); | |
| } | |
| } | |
| updateLoadingProgress(percent) { | |
| document.getElementById('loadingProgress').style.width = percent + '%'; | |
| } | |
| updateLoadingStatus(status) { | |
| document.getElementById('loadingStatus').textContent = status; | |
| } | |
| updateAIModelStatus(status) { | |
| const element = document.getElementById('aiModelStatus'); | |
| element.textContent = status; | |
| element.className = status === 'Listo' ? 'font-semibold text-green-600' : 'font-semibold text-red-600'; | |
| } | |
| renderDevices() { | |
| const container = document.getElementById('devicesContainer'); | |
| container.innerHTML = ''; | |
| this.devices.forEach(device => { | |
| const statusColor = { | |
| 'connected': 'bg-green-100 text-green-800', | |
| 'playing': 'bg-blue-100 text-blue-800', | |
| 'paused': 'bg-yellow-100 text-yellow-800', | |
| 'offline': 'bg-red-100 text-red-800' | |
| }[device.status] || 'bg-gray-100 text-gray-800'; | |
| const statusIcon = { | |
| 'connected': 'fa-check-circle', | |
| 'playing': 'fa-play-circle', | |
| 'paused': 'fa-pause-circle', | |
| 'offline': 'fa-times-circle' | |
| }[device.status] || 'fa-question-circle'; | |
| const deviceCard = ` | |
| <div class="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h4 class="font-bold text-lg flex items-center gap-2"> | |
| <i class="fas fa-tv text-purple-600"></i> | |
| ${device.name} | |
| </h4> | |
| <span class="px-3 py-1 rounded-full text-sm ${statusColor}"> | |
| <i class="fas ${statusIcon} mr-1"></i> | |
| ${device.status} | |
| </span> | |
| </div> | |
| <div class="space-y-2 text-sm"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-600">Ubicación:</span> | |
| <span class="font-medium">${device.location}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-600">Tipo:</span> | |
| <span class="font-medium">${device.type}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-600">Contenido:</span> | |
| <span class="font-medium truncate ml-2">${device.currentContent}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-600">Volumen:</span> | |
| <span class="font-medium">${device.volume}% ${device.isMuted ? '🔇' : '🔊'}</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| container.innerHTML += deviceCard; | |
| }); | |
| } | |
| renderIndividualControls() { | |
| const container = document.getElementById('individualControls'); | |
| container.innerHTML = ''; | |
| this.devices.forEach(device => { | |
| const controls = ` | |
| <div class="border border-gray-200 rounded-lg p-4"> | |
| <h4 class="font-semibold mb-3">${device.name}</h4> | |
| <div class="grid grid-cols-3 gap-2"> | |
| <button onclick="controlDevice(${device.id}, 'play')" | |
| class="bg-green-600 hover:bg-green-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| <button onclick="controlDevice(${device.id}, 'pause')" | |
| class="bg-yellow-600 hover:bg-yellow-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-pause"></i> | |
| </button> | |
| <button onclick="controlDevice(${device.id}, 'stop')" | |
| class="bg-red-600 hover:bg-red-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-stop"></i> | |
| </button> | |
| <button onclick="controlDevice(${device.id}, 'mute')" | |
| class="bg-gray-600 hover:bg-gray-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-volume-mute"></i> | |
| </button> | |
| <button onclick="controlDevice(${device.id}, 'volumeUp')" | |
| class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-volume-up"></i> | |
| </button> | |
| <button onclick="controlDevice(${device.id}, 'volumeDown')" | |
| class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-volume-down"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| container.innerHTML += controls; | |
| }); | |
| } | |
| renderPlaylist() { | |
| const container = document.getElementById('playlistContainer'); | |
| container.innerHTML = ''; | |
| this.playlist.forEach((video, index) => { | |
| const item = ` | |
| <div class="flex items-center gap-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer" | |
| onclick="playFromPlaylist(${index})"> | |
| <img src="${video.thumbnail}" alt="${video.title}" class="w-12 h-8 rounded object-cover"> | |
| <div class="flex-1"> | |
| <h5 class="font-semibold text-sm">${video.title}</h5> | |
| <p class="text-xs text-gray-600">${video.source} • ${video.duration}</p> | |
| </div> | |
| <button class="bg-purple-600 hover:bg-purple-700 text-white p-2 rounded text-sm transition-colors"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| </div> | |
| `; | |
| container.innerHTML += item; | |
| }); | |
| } | |
| async loadVideo(url, title = '') { | |
| if (!url) { | |
| this.showStatus('❌ Por favor, introduce una URL válida', 'error'); | |
| return; | |
| } | |
| this.currentVideo = { | |
| url: url, | |
| title: title || this.extractVideoTitle(url) | |
| }; | |
| // Update all devices | |
| this.devices.forEach(device => { | |
| device.currentContent = this.currentVideo.title; | |
| device.status = 'connected'; | |
| }); | |
| this.renderDevices(); | |
| this.showStatus(`✅ Video cargado: ${this.currentVideo.title}`, 'success'); | |
| document.getElementById('videoUrl').value = url; | |
| } | |
| extractVideoTitle(url) { | |
| if (url.includes('youtube.com')) return 'Video de YouTube'; | |
| if (url.includes('vimeo.com')) return 'Video de Vimeo'; | |
| if (url.includes('.mp4')) return 'Video Directo MP4'; | |
| return 'Video Online'; | |
| } | |
| playAll() { | |
| if (!this.currentVideo) { | |
| this.showStatus('❌ No hay video cargado', 'error'); | |
| return; | |
| } | |
| this.isPlaying = true; | |
| this.devices.forEach(device => { | |
| device.status = 'playing'; | |
| device.isPlaying = true; | |
| }); | |
| this.renderDevices(); | |
| this.showStatus('▶️ Reproduciendo en todos los dispositivos', 'success'); | |
| this.updateSystemStatus('Reproduciendo'); | |
| } | |
| pauseAll() { | |
| this.isPlaying = false; | |
| this.devices.forEach(device => { | |
| device.status = 'connected'; | |
| device.isPlaying = false; | |
| }); | |
| this.renderDevices(); | |
| this.showStatus('⏸️ Pausado en todos los dispositivos', 'info'); | |
| this.updateSystemStatus('Pausado'); | |
| } | |
| stopAll() { | |
| this.isPlaying = false; | |
| this.progress = 0; | |
| this.devices.forEach(device => { | |
| device.status = 'connected'; | |
| device.isPlaying = false; | |
| device.currentContent = 'Esperando...'; | |
| }); | |
| this.renderDevices(); | |
| this.updateProgress(); | |
| this.showStatus('⏹️ Detenido en todos los dispositivos', 'info'); | |
| this.updateSystemStatus('Detenido'); | |
| } | |
| controlDevice(deviceId, action) { | |
| const device = this.devices.find(d => d.id === deviceId); | |
| if (!device) return; | |
| switch (action) { | |
| case 'play': | |
| if (!this.currentVideo) { | |
| this.showStatus('❌ No hay video cargado', 'error'); | |
| return; | |
| } | |
| device.status = 'playing'; | |
| device.isPlaying = true; | |
| this.showStatus(`▶️ Reproduciendo en ${device.name}`, 'success'); | |
| break; | |
| case 'pause': | |
| device.status = 'connected'; | |
| device.isPlaying = false; | |
| this.showStatus(`⏸️ Pausado en ${device.name}`, 'info'); | |
| break; | |
| case 'stop': | |
| device.status = 'connected'; | |
| device.isPlaying = false; | |
| device.currentContent = 'Esperando...'; | |
| this.showStatus(`⏹️ Detenido en ${device.name}`, 'info'); | |
| break; | |
| case 'mute': | |
| device.isMuted = !device.isMuted; | |
| this.showStatus(`🔇 ${device.isMuted ? 'Silenciado' : 'Activado'} en ${device.name}`, 'info'); | |
| break; | |
| case 'volumeUp': | |
| device.volume = Math.min(100, device.volume + 10); | |
| this.showStatus(`🔊 Volumen ${device.volume}% en ${device.name}`, 'info'); | |
| break; | |
| case 'volumeDown': | |
| device.volume = Math.max(0, device.volume - 10); | |
| this.showStatus(`🔉 Volumen ${device.volume}% en ${device.name}`, 'info'); | |
| break; | |
| } | |
| this.renderDevices(); | |
| } | |
| changeVolume(volume) { | |
| this.volume = parseInt(volume); | |
| this.devices.forEach(device => { | |
| device.volume = this.volume; | |
| }); | |
| document.getElementById('volumePercentage').textContent = volume + '%'; | |
| this.renderDevices(); | |
| this.showStatus(`🔊 Volumen ajustado a ${volume}%`, 'info'); | |
| } | |
| muteAll() { | |
| const allMuted = this.devices.every(d => d.isMuted); | |
| this.devices.forEach(device => { | |
| device.isMuted = !allMuted; | |
| }); | |
| this.renderDevices(); | |
| this.showStatus(`🔇 ${allMuted ? 'Activado' : 'Silenciado'} en todos los dispositivos`, 'info'); | |
| } | |
| setVolume(volume) { | |
| document.getElementById('volumeSlider').value = volume; | |
| this.changeVolume(volume); | |
| } | |
| async analyzeContent() { | |
| if (!this.modelLoaded) { | |
| this.showStatus('❌ Modelo AI no disponible', 'error'); | |
| return; | |
| } | |
| if (!this.currentVideo) { | |
| this.showStatus('❌ No hay video para analizar', 'error'); | |
| return; | |
| } | |
| this.showStatus('🧠 Analizando contenido con AI...', 'info'); | |
| try { | |
| // Simulate content analysis | |
| const analysisText = `El video "${this.currentVideo.title}" es un contenido de alta calidad`; | |
| const result = await this.aiModel(analysisText); | |
| const analysisHTML = ` | |
| <div class="space-y-2"> | |
| <p><strong>Título:</strong> ${this.currentVideo.title}</p> | |
| <p><strong>URL:</strong> ${this.currentVideo.url}</p> | |
| <p><strong>Sentimiento:</strong> ${result[0].label} (${(result[0].score * 100).toFixed(1)}%)</p> | |
| <p><strong>Calidad:</strong> Alta definición detectada</p> | |
| <p><strong>Categoría:</strong> ${this.detectCategory()}</p> | |
| <p><strong>Duración recomendada:</strong> ${this.recommendDuration()}</p> | |
| </div> | |
| `; | |
| document.getElementById('analysisResults').innerHTML = analysisHTML; | |
| document.getElementById('aiAnalysis').classList.remove('hidden'); | |
| this.showStatus('✅ Análisis completado exitosamente', 'success'); | |
| } catch (error) { | |
| console.error('Analysis error:', error); | |
| this.showStatus('❌ Error en el análisis AI', 'error'); | |
| } | |
| } | |
| detectCategory() { | |
| if (!this.currentVideo) return 'Desconocida'; | |
| const title = this.currentVideo.title.toLowerCase(); | |
| if (title.includes('promocional')) return 'Marketing/Publicidad'; | |
| if (title.includes('corporativo')) return 'Corporativo'; | |
| if (title.includes('evento')) return 'Eventos'; | |
| return 'General'; | |
| } | |
| recommendDuration() { | |
| if (!this.currentVideo) return 'N/A'; | |
| const category = this.detectCategory(); | |
| const recommendations = { | |
| 'Marketing/Publicidad': '30-60 segundos', | |
| 'Corporativo': '2-5 minutos', | |
| 'Eventos': '3-10 minutos', | |
| 'General': '1-5 minutos' | |
| }; | |
| return recommendations[category] || '1-5 minutos'; | |
| } | |
| addToPlaylist(url, title = '') { | |
| if (!url) { | |
| this.showStatus('❌ Introduce una URL válida', 'error'); | |
| return; | |
| } | |
| const video = { | |
| title: title || this.extractVideoTitle(url), | |
| source: this.extractSource(url), | |
| url: url, | |
| duration: '--:--', | |
| thumbnail: `https://picsum.photos/seed/${Date.now()}/60/40` | |
| }; | |
| this.playlist.push(video); | |
| this.renderPlaylist(); | |
| this.showStatus(`✅ "${video.title}" agregado a la playlist`, 'success'); | |
| // Clear inputs | |
| document.getElementById('playlistUrl').value = ''; | |
| document.getElementById('playlistTitle').value = ''; | |
| } | |
| extractSource(url) { | |
| if (url.includes('youtube.com')) return 'YouTube'; | |
| if (url.includes('vimeo.com')) return 'Vimeo'; | |
| if (url.includes('.mp4')) return 'Directo'; | |
| return 'Online'; | |
| } | |
| playFromPlaylist(index) { | |
| if (index >= 0 && index < this.playlist.length) { const video=this.playlist[index]; this.loadVideo(video.url, | |
| video.title); this.playAll(); } } loadSampleVideo(type) { const samples={ | |
| youtube: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' , vimeo: 'https://vimeo.com/123456789' , | |
| direct: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' }; const | |
| url=samples[type]; this.loadVideo(url); } discoverDevices() { this.showStatus('🔍 Buscando dispositivos...', 'info' ); | |
| // Simulate device discovery setTimeout(()=> { | |
| this.renderDevices(); | |
| this.showStatus(`✅ ${this.devices.length} dispositivos encontrados`, 'success'); | |
| }, 1500); | |
| } | |
| refreshStatus() { | |
| this.renderDevices(); | |
| this.showStatus('🔄 Estado actualizado', 'info'); | |
| } | |
| rewind() { | |
| this.progress = Math.max(0, this.progress - 10); | |
| this.updateProgress(); | |
| this.showStatus('⏪ Retrocediendo 10 segundos', 'info'); | |
| } | |
| forward() { | |
| this.progress = Math.min(100, this.progress + 10); | |
| this.updateProgress(); | |
| this.showStatus('⏩ Adelantando 10 segundos', 'info'); | |
| } | |
| updateProgress() { | |
| const progressBar = document.getElementById('progressBar'); | |
| const currentTime = document.getElementById('currentTime'); | |
| progressBar.style.width = this.progress + '%'; | |
| const totalSeconds = 225; // 3:45 in seconds | |
| const currentSeconds = Math.floor((this.progress / 100) * totalSeconds); | |
| const minutes = Math.floor(currentSeconds / 60); | |
| const seconds = currentSeconds % 60; | |
| currentTime.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
| } | |
| startProgressUpdater() { | |
| setInterval(() => { | |
| if (this.isPlaying && this.progress < 100) { this.progress +=0.5; this.updateProgress(); } }, 1000); } | |
| showStatus(message, type='info' ) { const statusElement=document.getElementById('statusMessage'); | |
| statusElement.textContent=message; // Update styling based on type | |
| statusElement.className='mt-3 p-3 rounded-lg text-sm ' ; switch (type) { case 'success' : statusElement.className | |
| +='bg-green-100 text-green-700' ; break; case 'error' : statusElement.className +='bg-red-100 text-red-700' ; break; | |
| case 'warning' : statusElement.className +='bg-yellow-100 text-yellow-700' ; break; default: statusElement.className | |
| +='bg-gray-100 text-gray-700' ; } } updateSystemStatus(status) { const | |
| element=document.getElementById('systemStatus'); element.textContent=status; element.className='font-semibold ' ; | |
| switch (status) { case 'Reproduciendo' : element.className +='text-blue-600' ; break; case 'Pausado' : | |
| element.className +='text-yellow-600' ; break; case 'Detenido' : element.className +='text-gray-600' ; break; | |
| default: element.className +='text-green-600' ; } } } // Global controller instance let controller; // Initialize | |
| when DOM is loaded document.addEventListener('DOMContentLoaded', ()=> { | |
| controller = new MultimediaController(); | |
| }); | |
| // Global functions for button onclick handlers | |
| function loadVideo() { | |
| const url = document.getElementById('videoUrl').value; | |
| controller.loadVideo(url); | |
| } | |
| function loadSampleVideo(type) { | |
| controller.loadSampleVideo(type); | |
| } | |
| function playAll() { | |
| controller.playAll(); | |
| } | |
| function pauseAll() { | |
| controller.pauseAll(); | |
| } | |
| function stopAll() { | |
| controller.stopAll(); | |
| } | |
| function rewind() { | |
| controller.rewind(); | |
| } | |
| function forward() { | |
| controller.forward(); | |
| } | |
| function changeVolume(value) { | |
| controller.changeVolume(value); | |
| } | |
| function muteAll() { | |
| controller.muteAll(); | |
| } | |
| function setVolume(value) { | |
| controller.setVolume(value); | |
| } | |
| function analyzeContent() { | |
| controller.analyzeContent(); | |
| } | |
| function addToPlaylist() { | |
| const url = document.getElementById('playlistUrl').value; | |
| const title = document.getElementById('playlistTitle').value; | |
| controller.addToPlaylist(url, title); | |
| } | |
| function playFromPlaylist(index) { | |
| controller.playFromPlaylist(index); | |
| } | |
| function controlDevice(deviceId, action) { | |
| controller.controlDevice(deviceId, action); | |
| } | |
| function discoverDevices() { | |
| controller.discoverDevices(); | |
| } | |
| function refreshStatus() { | |
| controller.refreshStatus(); | |
| } |