Spaces:
Running
Running
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Control Multimedia Chromecast - Hostelería</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary: #2563eb; | |
| --secondary: #7c3aed; | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --danger: #ef4444; | |
| --dark: #1f2937; | |
| --light: #f3f4f6; | |
| --card-bg: rgba(255, 255, 255, 0.95); | |
| --gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| --gradient-2: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| } | |
| body { | |
| font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; | |
| background: linear-gradient(135deg, #1e3a8a 0%, #312e81 50%, #581c87 100%); | |
| min-height: 100vh; | |
| color: var(--dark); | |
| position: relative; | |
| overflow-x: hidden; | |
| } | |
| /* Animated background */ | |
| .bg-animation { | |
| position: fixed; | |
| width: 100%; | |
| height: 100%; | |
| top: 0; | |
| left: 0; | |
| z-index: -1; | |
| opacity: 0.1; | |
| } | |
| .floating-shape { | |
| position: absolute; | |
| border-radius: 50%; | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| animation: float 20s infinite ease-in-out; | |
| } | |
| @keyframes float { | |
| 0%, | |
| 100% { | |
| transform: translateY(0) rotate(0deg); | |
| } | |
| 33% { | |
| transform: translateY(-30px) rotate(120deg); | |
| } | |
| 66% { | |
| transform: translateY(30px) rotate(240deg); | |
| } | |
| } | |
| /* Header */ | |
| header { | |
| background: var(--card-bg); | |
| backdrop-filter: blur(20px); | |
| padding: 1.5rem; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| animation: slideDown 0.5s ease-out; | |
| } | |
| @keyframes slideDown { | |
| from { | |
| transform: translateY(-100%); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| } | |
| .header-content { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| h1 { | |
| font-size: 1.8rem; | |
| background: var(--gradient); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .status-indicators { | |
| display: flex; | |
| gap: 1rem; | |
| align-items: center; | |
| } | |
| .status-badge { | |
| padding: 0.5rem 1rem; | |
| border-radius: 20px; | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| animation: pulse 2s infinite; | |
| } | |
| .status-badge.online { | |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); | |
| color: white; | |
| } | |
| .status-badge.streaming { | |
| background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
| color: white; | |
| } | |
| @keyframes pulse { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.8; | |
| } | |
| } | |
| /* Main container */ | |
| .container { | |
| max-width: 1400px; | |
| margin: 2rem auto; | |
| padding: 0 1rem; | |
| } | |
| /* TV Status Grid */ | |
| .tv-status-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| margin-bottom: 2rem; | |
| } | |
| .tv-card { | |
| background: var(--card-bg); | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| transition: all 0.3s; | |
| animation: fadeInUp 0.6s ease-out; | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| transform: translateY(30px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| } | |
| .tv-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15); | |
| } | |
| .tv-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| } | |
| .tv-name { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: var(--dark); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .tv-status { | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 15px; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| } | |
| .tv-status.connected { | |
| background: #dcfce7; | |
| color: #16a34a; | |
| } | |
| .tv-status.disconnected { | |
| background: #fee2e2; | |
| color: #dc2626; | |
| } | |
| .tv-status.playing { | |
| background: #fef3c7; | |
| color: #d97706; | |
| } | |
| .tv-info { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .info-item { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 0.5rem; | |
| background: var(--light); | |
| border-radius: 10px; | |
| font-size: 0.9rem; | |
| } | |
| .info-label { | |
| font-weight: 600; | |
| color: #6b7280; | |
| } | |
| .info-value { | |
| color: var(--dark); | |
| } | |
| .tv-controls { | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| } | |
| /* Control Panel */ | |
| .control-panel { | |
| background: var(--card-bg); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| margin-bottom: 2rem; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| } | |
| .panel-header { | |
| font-size: 1.3rem; | |
| font-weight: 700; | |
| margin-bottom: 1.5rem; | |
| color: var(--dark); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .video-input-section { | |
| margin-bottom: 2rem; | |
| } | |
| .input-group { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| .video-input { | |
| flex: 1; | |
| padding: 1rem; | |
| font-size: 1rem; | |
| border: 2px solid #e5e7eb; | |
| border-radius: 12px; | |
| transition: all 0.3s; | |
| } | |
| .video-input:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); | |
| } | |
| /* Buttons */ | |
| .btn { | |
| padding: 0.8rem 1.5rem; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| border: none; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); | |
| color: white; | |
| } | |
| .btn-success { | |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); | |
| color: white; | |
| } | |
| .btn-warning { | |
| background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
| color: white; | |
| } | |
| .btn-danger { | |
| background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); | |
| color: white; | |
| } | |
| .btn-secondary { | |
| background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); | |
| color: white; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| .btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Media Controls */ | |
| .media-controls { | |
| display: flex; | |
| gap: 1rem; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| margin-bottom: 2rem; | |
| } | |
| .control-btn { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| } | |
| .volume-control { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| padding: 1rem; | |
| background: var(--light); | |
| border-radius: 12px; | |
| } | |
| .volume-slider { | |
| flex: 1; | |
| height: 6px; | |
| border-radius: 3px; | |
| background: #e5e7eb; | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| .volume-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: var(--primary); | |
| cursor: pointer; | |
| } | |
| /* Playlist */ | |
| .playlist-section { | |
| background: var(--card-bg); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| } | |
| .playlist-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| } | |
| .playlist-items { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .playlist-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 1rem; | |
| background: var(--light); | |
| border-radius: 12px; | |
| transition: all 0.3s; | |
| cursor: pointer; | |
| } | |
| .playlist-item:hover { | |
| background: #e5e7eb; | |
| transform: translateX(5px); | |
| } | |
| .playlist-item.active { | |
| background: linear-gradient(135deg, #ddd6fe 0%, #c4b5fd 100%); | |
| color: var(--dark); | |
| } | |
| .playlist-info { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .playlist-thumbnail { | |
| width: 60px; | |
| height: 40px; | |
| border-radius: 8px; | |
| object-fit: cover; | |
| } | |
| .playlist-text h4 { | |
| font-size: 1rem; | |
| margin-bottom: 0.2rem; | |
| } | |
| .playlist-text p { | |
| font-size: 0.85rem; | |
| color: #6b7280; | |
| } | |
| /* Progress bar */ | |
| .progress-container { | |
| margin-bottom: 1.5rem; | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 8px; | |
| background: #e5e7eb; | |
| border-radius: 4px; | |
| overflow: hidden; | |
| cursor: pointer; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%); | |
| border-radius: 4px; | |
| transition: width 0.1s; | |
| position: relative; | |
| } | |
| .progress-time { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-top: 0.5rem; | |
| font-size: 0.85rem; | |
| color: #6b7280; | |
| } | |
| /* Quick actions */ | |
| .quick-actions { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .action-card { | |
| background: var(--card-bg); | |
| border-radius: 15px; | |
| padding: 1.5rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); | |
| } | |
| .action-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); | |
| } | |
| .action-icon { | |
| font-size: 2.5rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .action-title { | |
| font-weight: 600; | |
| color: var(--dark); | |
| margin-bottom: 0.3rem; | |
| } | |
| .action-desc { | |
| font-size: 0.85rem; | |
| color: #6b7280; | |
| } | |
| /* Toast notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: white; | |
| padding: 1rem 1.5rem; | |
| border-radius: 12px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| transform: translateX(400px); | |
| transition: transform 0.3s; | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| .toast-icon { | |
| font-size: 1.5rem; | |
| } | |
| .toast.success { | |
| border-left: 4px solid var(--success); | |
| } | |
| .toast.error { | |
| border-left: 4px solid var(--danger); | |
| } | |
| .toast.info { | |
| border-left: 4px solid var(--primary); | |
| } | |
| /* Footer */ | |
| footer { | |
| text-align: center; | |
| padding: 2rem; | |
| color: white; | |
| margin-top: 3rem; | |
| } | |
| .built-with { | |
| color: white; | |
| text-decoration: none; | |
| font-weight: 600; | |
| background: rgba(255, 255, 255, 0.2); | |
| padding: 0.5rem 1rem; | |
| border-radius: 10px; | |
| display: inline-block; | |
| transition: all 0.3s; | |
| } | |
| .built-with:hover { | |
| background: rgba(255, 255, 255, 0.3); | |
| transform: translateY(-2px); | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| h1 { | |
| font-size: 1.3rem; | |
| } | |
| .tv-status-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .quick-actions { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| .header-content { | |
| flex-direction: column; | |
| text-align: center; | |
| } | |
| .status-indicators { | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| } | |
| /* Loading spinner */ | |
| .spinner { | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: white; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| Modal .modal { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.5); | |
| display: none; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 2000; | |
| } | |
| .modal.show { | |
| display: flex; | |
| } | |
| .modal-content { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 2rem; | |
| max-width: 500px; | |
| width: 90%; | |
| animation: popIn 0.3s ease-out; | |
| } | |
| @keyframes popIn { | |
| from { | |
| transform: scale(0.8); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: scale(1); | |
| opacity: 1; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Animated background --> | |
| <div class="bg-animation"> | |
| <div class="floating-shape" style="width: 100px; height: 100px; top: 10%; left: 10%; animation-delay: 0s;"></div> | |
| <div class="floating-shape" style="width: 150px; height: 150px; top: 70%; right: 10%; animation-delay: 5s;"></div> | |
| <div class="floating-shape" style="width: 80px; height: 80px; bottom: 20%; left: 50%; animation-delay: 10s;"></div> | |
| </div> | |
| <!-- Header --> | |
| <header> | |
| <div class="header-content"> | |
| <h1> | |
| 📺 Control Multimedia Chromecast | |
| </h1> | |
| <div class="status-indicators"> | |
| <div class="status-badge online"> | |
| <span>🟢</span> | |
| <span>3 TVs Conectadas</span> | |
| </div> | |
| <div class="status-badge streaming" id="streamingBadge" style="display: none;"> | |
| <span>🔴</span> | |
| <span>Transmitiendo</span> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main container --> | |
| <div class="container"> | |
| <!-- Quick Actions --> | |
| <div class="quick-actions"> | |
| <div class="action-card" onclick="playAllTvs()"> | |
| <div class="action-icon">▶️</div> | |
| <div class="action-title">Reproducir</div> | |
| <div class="action-desc">En todas las TVs</div> | |
| </div> | |
| <div class="action-card" onclick="pauseAllTvs()"> | |
| <div class="action-icon">⏸️</div> | |
| <div class="action-title">Pausar</div> | |
| <div class="action-desc">Detener reproducción</div> | |
| </div> | |
| <div class="action-card" onclick="stopAllTvs()"> | |
| <div class="action-icon">⏹️</div> | |
| <div class="action-title">Detener</div> | |
| <div class="action-desc">Finalizar transmisión</div> | |
| </div> | |
| <div class="action-card" onclick="muteAllTvs()"> | |
| <div class="action-icon">🔇</div> | |
| <div class="action-title">Silenciar</div> | |
| <div class="action-desc">Todas las TVs</div> | |
| </div> | |
| </div> | |
| <!-- TV Status Grid --> | |
| <div class="tv-status-grid"> | |
| <div class="tv-card"> | |
| <div class="tv-header"> | |
| <div class="tv-name"> | |
| <span>📺</span> | |
| <span>TV - Sala Principal</span> | |
| </div> | |
| <div class="tv-status connected" id="tv1-status">Conectado</div> | |
| </div> | |
| <div class="tv-info"> | |
| <div class="info-item"> | |
| <span class="info-label">Dispositivo:</span> | |
| <span class="info-value">Chromecast Ultra</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Reproduciendo:</span> | |
| <span class="info-value" id="tv1-content">Esperando...</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Volumen:</span> | |
| <span class="info-value" id="tv1-volume">50%</span> | |
| </div> | |
| </div> | |
| <div class="tv-controls"> | |
| <button class="btn btn-success control-btn" onclick="controlTv(1, 'play')">▶️</button> | |
| <button class="btn btn-warning control-btn" onclick="controlTv(1, 'pause')">⏸️</button> | |
| <button class="btn btn-danger control-btn" onclick="controlTv(1, 'stop')">⏹️</button> | |
| <button class="btn btn-secondary control-btn" onclick="controlTv(1, 'mute')">🔇</button> | |
| </div> | |
| </div> | |
| <div class="tv-card"> | |
| <div class="tv-header"> | |
| <div class="tv-name"> | |
| <span>📺</span> | |
| <span>TV - Barra</span> | |
| </div> | |
| <div class="tv-status connected" id="tv2-status">Conectado</div> | |
| </div> | |
| <div class="tv-info"> | |
| <div class="info-item"> | |
| <span class="info-label">Dispositivo:</span> | |
| <span class="info-value">Chromecast 4K</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Reproduciendo:</span> | |
| <span class="info-value" id="tv2-content">Esperando...</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Volumen:</span> | |
| <span class="info-value" id="tv2-volume">50%</span> | |
| </div> | |
| </div> | |
| <div class="tv-controls"> | |
| <button class="btn btn-success control-btn" onclick="controlTv(2, 'play')">▶️</button> | |
| <button class="btn btn-warning control-btn" onclick="controlTv(2, 'pause')">⏸️</button> | |
| <button class="btn btn-danger control-btn" onclick="controlTv(2, 'stop')">⏹️</button> | |
| <button class="btn btn-secondary control-btn" onclick="controlTv(2, 'mute')">🔇</button> | |
| </div> | |
| </div> | |
| <div class="tv-card"> | |
| <div class="tv-header"> | |
| <div class="tv-name"> | |
| <span>📺</span> | |
| <span>TV - Terraza</span> | |
| </div> | |
| <div class="tv-status connected" id="tv3-status">Conectado</div> | |
| </div> | |
| <div class="tv-info"> | |
| <div class="info-item"> | |
| <span class="info-label">Dispositivo:</span> | |
| <span class="info-value">Chromecast with Google TV</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Reproduciendo:</span> | |
| <span class="info-value" id="tv3-content">Esperando...</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Volumen:</span> | |
| <span class="info-value" id="tv3-volume">50%</span> | |
| </div> | |
| </div> | |
| <div class="tv-controls"> | |
| <button class="btn btn-success control-btn" onclick="controlTv(3, 'play')">▶️</button> | |
| <button class="btn btn-warning control-btn" onclick="controlTv(3, 'pause')">⏸️</button> | |
| <button class="btn btn-danger control-btn" onclick="controlTv(3, 'stop')">⏹️</button> | |
| <button class="btn btn-secondary control-btn" onclick="controlTv(3, 'mute')">🔇</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Control Panel --> | |
| <div class="control-panel"> | |
| <h2 class="panel-header"> | |
| <span>🎬</span> | |
| <span>Control de Reproducción</span> | |
| </h2> | |
| <!-- Video Input --> | |
| <div class="video-input-section"> | |
| <div class="input-group"> | |
| <input type="text" class="video-input" id="videoUrl" placeholder="Introduce URL del video (YouTube, Vimeo, URL directa)"> | |
| <button class="btn btn-primary" onclick="loadVideo()"> | |
| <span>📥</span> | |
| <span>Cargar Video</span> | |
| </button> | |
| </div> | |
| <div class="input-group"> | |
| <button class="btn btn-secondary" onclick="loadSampleVideo('youtube')"> | |
| <span>📺</span> | |
| <span>YouTube Sample</span> | |
| </button> | |
| <button class="btn btn-secondary" onclick="loadSampleVideo('vimeo')"> | |
| <span>🎥</span> | |
| <span>Vimeo Sample</span> | |
| </button> | |
| <button class="btn btn-secondary" onclick="loadSampleVideo('direct')"> | |
| <span>🔗</span> | |
| <span>URL Directa</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Progress Bar --> | |
| <div class="progress-container"> | |
| <div class="progress-bar" onclick="seekVideo(event)"> | |
| <div class="progress-fill" id="progressFill" style="width: 0%"></div> | |
| </div> | |
| <div class="progress-time"> | |
| <span id="currentTime">00:00</span> | |
| <span id="duration">00:00</span> | |
| </div> | |
| </div> | |
| <!-- Media Controls --> | |
| <div class="media-controls"> | |
| <button class="btn btn-secondary control-btn" onclick="rewind()">⏪</button> | |
| <button class="btn btn-success control-btn" onclick="playAllTvs()">▶️</button> | |
| <button class="btn btn-warning control-btn" onclick="pauseAllTvs()">⏸️</button> | |
| <button class="btn btn-danger control-btn" onclick="stopAllTvs()">⏹️</button> | |
| <button class="btn btn-secondary control-btn" onclick="forward()">⏩</button> | |
| </div> | |
| <!-- Volume Control --> | |
| <div class="volume-control"> | |
| <span>🔊</span> | |
| <input type="range" class="volume-slider" id="masterVolume" min="0" max="100" value="50" onchange="changeVolume(this.value)"> | |
| <span id="volumePercentage">50%</span> | |
| </div> | |
| </div> | |
| <!-- Playlist --> | |
| <div class="playlist-section"> | |
| <div class="playlist-header"> | |
| <h2 class="panel-header"> | |
| <span>📋</span> | |
| <span>Playlist de Videos</span> | |
| </h2> | |
| <button class="btn btn-primary" onclick="addToPlaylist()"> | |
| <span>➕</span> | |
| <span>Agregar a Playlist</span> | |
| </button> | |
| </div> | |
| <div class="playlist-items" id="playlistItems"> | |
| <div class="playlist-item active" | |
| onclick="playFromPlaylist(this, 'https://www.youtube.com/watch?v=dQw4w9WgXcQ')"> | |
| <div class="playlist-info"> | |
| <img src="https://picsum.photos/seed/video1/60/40" alt="Thumbnail" class="playlist-thumbnail"> | |
| <div class="playlist-text"> | |
| <h4>Video Promocional</h4> | |
| <p>YouTube • 3:45</p> | |
| </div> | |
| </div> | |
| <button class="btn btn-success" onclick="event.stopPropagation(); playVideoNow(this)">▶️</button> | |
| </div> | |
| <div class="playlist-item" onclick="playFromPlaylist(this, 'https://vimeo.com/123456789')"> | |
| <div class="playlist-info"> | |
| <img src="https://picsum.photos/seed/video2/60/40" alt="Thumbnail" class="playlist-thumbnail"> | |
| <div class="playlist-text"> | |
| <h4>Video Corporativo</h4> | |
| <p>Vimeo • 2:30</p> | |
| </div> | |
| </div> | |
| <button class="btn btn-success" onclick="event.stopPropagation(); playVideoNow(this)">▶️</button> | |
| </div> | |
| <div class="playlist-item" onclick="playFromPlaylist(this, 'https://example.com/video.mp4')"> | |
| <div class="playlist-info"> | |
| <img src="https://picsum.photos/seed/video3/60/40" alt="Thumbnail" class="playlist-thumbnail"> | |
| <div class="playlist-text"> | |
| <h4>Video Evento</h4> | |
| <p>Directo • 5:15</p> | |
| </div> | |
| </div> | |
| <button class="btn btn-success" onclick="event.stopPropagation(); playVideoNow(this)">▶️</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div class="toast" id="toast"> | |
| <span class="toast-icon" id="toastIcon">✅</span> | |
| <div> | |
| <div id="toastTitle">Operación Exitosa</div> | |
| <div id="toastMessage" style="font-size: 0.85rem; color: #6b7280;">Acción completada</div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with"> | |
| Built with anycoder | |
| </a> | |
| </footer> | |
| <script> | |
| // Global state | |
| let isPlaying = false; | |
| let currentVideo = null; | |
| let tvStates = { | |
| 1: { connected: true, playing: false, volume: 50, content: 'Esperando...' }, | |
| 2: { connected: true, playing: false, volume: 50, content: 'Esperando...' }, | |
| 3: { connected: true, playing: false, volume: 50, content: 'Esperando...' } | |
| }; | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| simulateConnection(); | |
| updateProgress(); | |
| }); | |
| // Simulate initial connection | |
| function simulateConnection() { | |
| showToast('Conectando a dispositivos...', 'info'); | |
| setTimeout(() => { | |
| showToast('Todos los dispositivos conectados correctamente', 'success'); | |
| }, 2000); | |
| } | |
| // Load video | |
| function loadVideo() { | |
| const url = document.getElementById('videoUrl').value; | |
| if (!url) { | |
| showToast('Por favor, introduce una URL válida', 'error'); | |
| return; | |
| } | |
| currentVideo = url; | |
| showToast('Cargando video...', 'info'); | |
| setTimeout(() => { | |
| updateAllTvsContent(extractVideoName(url)); | |
| document.getElementById('streamingBadge').style.display = 'flex'; | |
| showToast('Video cargado en todas las TVs', 'success'); | |
| }, 1500); | |
| } | |
| // Load sample videos | |
| function 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' | |
| }; | |
| document.getElementById('videoUrl').value = samples[type]; | |
| loadVideo(); | |
| } | |
| // Extract video name from URL | |
| function extractVideoName(url) { | |
| if (url.includes('youtube')) return 'Video de YouTube'; | |
| if (url.includes('vimeo')) return 'Video de Vimeo'; | |
| if (url.includes('.mp4')) return 'Video Directo'; | |
| return 'Video Online'; | |
| } | |
| // Control individual TV | |
| function controlTv(tvId, action) { | |
| const tv = tvStates[tvId]; | |
| switch(action) { | |
| case 'play': | |
| if (currentVideo) { | |
| tv.playing = true; | |
| updateTvStatus(tvId, 'playing'); | |
| showToast(`TV ${tvId} reproduciendo`, 'success'); | |
| } else { | |
| showToast('No hay video cargado', 'error'); | |
| } | |
| break; | |
| case 'pause': | |
| tv.playing = false; | |
| updateTvStatus(tvId, 'connected'); | |
| showToast(`TV ${tvId} pausado`, 'info'); | |
| break; | |
| case 'stop': | |
| tv.playing = false; | |
| tv.content = 'Esperando...'; | |
| updateTvInfo(tvId); | |
| updateTvStatus(tvId, 'connected'); | |
| showToast(`TV ${tvId} detenido`, 'info'); | |
| break; | |
| case 'mute': | |
| tv.volume = tv.volume > 0 ? 0 : 50; | |
| document.getElementById(`tv${tvId}-volume`).textContent = tv.volume + '%'; | |
| showToast(`TV ${tvId} silenciado/activado`, 'info'); | |
| break; | |
| } | |
| } | |
| // Control all TVs | |
| function playAllTvs() { | |
| if (!currentVideo) { | |
| showToast('Por favor, carga un video primero', 'error'); | |
| return; | |
| } | |
| for (let i = 1; i <= 3; i++) { | |
| tvStates[i].playing = true; | |
| updateTvStatus(i, 'playing'); | |
| } | |
| isPlaying = true; | |
| document.getElementById('streamingBadge').style.display = 'flex'; | |
| showToast('Reproduciendo en todas las TVs', 'success'); | |
| } | |
| function pauseAllTvs() { | |
| for (let i = 1; i <= 3; i++) { | |
| tvStates[i].playing = false; | |
| updateTvStatus(i, 'connected'); | |
| } | |
| isPlaying = false; | |
| showToast('Pausado en todas las TVs', 'info'); | |
| } | |
| function stopAllTvs() { | |
| for (let i = 1; i <= 3; i++) { | |
| tvStates[i].playing = false; | |
| tvStates[i].content = 'Esperando...'; | |
| updateTvInfo(i); | |
| updateTvStatus(i, 'connected'); | |
| } | |
| isPlaying = false; | |
| currentVideo = null; | |
| document.getElementById('streamingBadge').style.display = 'none'; | |
| document.getElementById('videoUrl').value = ''; | |
| showToast('Transmisión detenida', 'info'); | |
| } | |
| function muteAllTvs() { | |
| for (let i = 1; i <= 3; i++) { | |
| tvStates[i].volume = tvStates[i].volume > 0 ? 0 : 50; | |
| document.getElementById(`tv${i}-volume`).textContent = tvStates[i].volume + '%'; | |
| } | |
| showToast('Silencio toggled en todas las TVs', 'info'); | |
| } | |
| // Volume control | |
| function changeVolume(value) { | |
| for (let i = 1; i <= 3; i++) { | |
| tvStates[i].volume = value; | |
| document.getElementById(`tv${i}-volume`).textContent = value + '%'; | |
| } | |
| document.getElementById('volumePercentage').textContent = value + '%'; | |
| showToast(`Volumen ajustado a ${value}%`, 'info'); | |
| } | |
| // Update functions | |
| function updateTvStatus(tvId, status) { | |
| const statusElement = document.getElementById(`tv${tvId}-status`); | |
| statusElement.className = `tv-status ${status}`; | |
| const statusText = { | |
| connected: 'Conectado', | |
| playing: 'Reproduciendo', | |
| disconnected: 'Desconectado' | |
| }; | |
| statusElement.textContent = statusText[status]; | |
| } | |
| function updateTvInfo(tvId) { | |
| const tv = tvStates[tvId]; | |
| document.getElementById(`tv${tvId}-content`).textContent = tv.content; | |
| document.getElementById(`tv${tvId}-volume`).textContent = tv.volume + '%'; | |
| } | |
| function updateAllTvsContent(content) { | |
| for (let i = 1; i <= 3; i++) { | |
| tvStates[i].content = content; | |
| updateTvInfo(i); | |
| } | |
| } | |
| // Progress bar simulation | |
| function updateProgress() { | |
| let progress = 0; | |
| setInterval(() => { | |
| if (isPlaying && progress < 100) { | |
| progress += 0.5; | |
| document.getElementById('progressFill').style.width = progress + '%'; | |
| // Update time | |
| const current = Math.floor(progress * 2.25); | |
| const minutes = Math.floor(current / 60); | |
| const seconds = current % 60; | |
| document.getElementById('currentTime').textContent = | |
| `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; | |
| } | |
| }, 1000); | |
| } | |
| // Seek video | |
| function seekVideo(event) { | |
| const progressBar = event.currentTarget; | |
| const clickX = event.offsetX; | |
| const width = progressBar.offsetWidth; | |
| const percentage = (clickX / width) * 100; | |
| document.getElementById('progressFill').style.width = percentage + '%'; | |
| showToast(`Buscando: ${Math.round(percentage)}%`, 'info'); | |
| } | |
| // Playback controls | |
| function rewind() { | |
| showToast('Rebobinando 10 segundos', 'info'); | |
| const currentWidth = parseFloat(document.getElementById('progressFill').style.width) || 0; | |
| const newWidth = Math.max(0, currentWidth - 5); | |
| document.getElementById('progressFill').style.width = newWidth + '%'; | |
| } | |
| function forward() { | |
| showToast('Adelantando 10 segundos', 'info'); | |
| const currentWidth = parseFloat(document.getElementById('progressFill').style.width) || 0; | |
| const newWidth = Math.min(100, currentWidth + 5); | |
| document.getElementById('progressFill').style.width = newWidth + '%'; | |
| } | |
| // Playlist functions | |
| function playFromPlaylist(element, url) { | |
| document.querySelectorAll('.playlist-item').forEach(item => { | |
| item.classList.remove('active'); | |
| }); | |
| element.classList.add('active'); | |
| document.getElementById('videoUrl').value = url; | |
| loadVideo(); | |
| } | |
| function playVideoNow(button) { | |
| const item = button.closest('.playlist-item'); | |
| item.click(); | |
| } | |
| function addToPlaylist() { | |
| const url = document.getElementById('videoUrl').value; | |
| if (!url) { | |
| showToast('Introduce una URL primero', 'error'); | |
| return; | |
| } | |
| const playlist = document.getElementById('playlistItems'); | |
| const newItem = document.createElement('div'); | |
| newItem.className = 'playlist-item'; | |
| newItem.innerHTML = ` | |
| <div class="playlist-info"> | |
| <img src="https://picsum.photos/seed/${Date.now()}/60/40" alt="Thumbnail" class="playlist-thumbnail"> | |
| <div class="playlist-text"> | |
| <h4>Video Agregado</h4> | |
| <p>Custom • --:--</p> | |
| </div> | |
| </div> | |
| <button class="btn btn-danger" onclick="event.stopPropagation(); this.parentElement.remove()">🗑️</button> | |
| `; | |
| playlist.appendChild(newItem); | |
| showToast('Video agregado a la playlist', 'success'); | |
| } | |
| // Toast notification | |
| function showToast(message, type = 'success') { | |
| const toast = document.getElementById('toast'); | |
| const icon = document.getElementById('toastIcon'); | |
| const title = document.getElementById('toastTitle'); | |
| const icons = { | |
| success: '✅', | |
| error: '❌', | |
| info: 'ℹ️' | |
| }; | |
| const titles = { | |
| success: 'Operación Exitosa', | |
| error: 'Error', | |
| info: 'Información' | |
| }; | |
| toast.className = `toast ${type}`; | |
| icon.textContent = icons[type]; | |
| title.textContent = titles[type]; | |
| document.getElementById('toastMessage').textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> |