| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>LED控制面板</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: 'Arial', sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- padding: 20px;
- }
- .container {
- max-width: 800px;
- margin: 0 auto;
- background: white;
- border-radius: 15px;
- box-shadow: 0 10px 30px rgba(0,0,0,0.3);
- overflow: hidden;
- }
- .header {
- background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
- color: white;
- padding: 30px;
- text-align: center;
- }
- .header h1 {
- font-size: 2.5em;
- margin-bottom: 10px;
- }
- .header p {
- font-size: 1.1em;
- opacity: 0.9;
- }
- .content {
- padding: 30px;
- }
- .control-group {
- margin-bottom: 25px;
- padding: 20px;
- background: #f8f9fa;
- border-radius: 10px;
- border-left: 4px solid #4ecdc4;
- }
- .control-group h3 {
- color: #333;
- margin-bottom: 15px;
- font-size: 1.3em;
- }
- .button-group {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- }
- .btn {
- padding: 12px 24px;
- border: none;
- border-radius: 25px;
- cursor: pointer;
- font-size: 14px;
- font-weight: bold;
- transition: all 0.3s ease;
- text-transform: uppercase;
- letter-spacing: 1px;
- }
- .btn-primary {
- background: linear-gradient(45deg, #4ecdc4, #44a08d);
- color: white;
- }
- .btn-primary:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(78, 205, 196, 0.4);
- }
- .btn-danger {
- background: linear-gradient(45deg, #ff6b6b, #ee5a52);
- color: white;
- }
- .btn-danger:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
- }
- .btn-warning {
- background: linear-gradient(45deg, #feca57, #ff9ff3);
- color: white;
- }
- .btn-warning:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(254, 202, 87, 0.4);
- }
- .slider-container {
- display: flex;
- align-items: center;
- gap: 15px;
- margin: 15px 0;
- }
- .slider {
- flex: 1;
- height: 8px;
- border-radius: 5px;
- background: #ddd;
- outline: none;
- -webkit-appearance: none;
- }
- .slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background: #4ecdc4;
- cursor: pointer;
- }
- .slider::-moz-range-thumb {
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background: #4ecdc4;
- cursor: pointer;
- border: none;
- }
- .color-picker {
- width: 50px;
- height: 50px;
- border: none;
- border-radius: 50%;
- cursor: pointer;
- margin: 0 10px;
- }
- .status-display {
- background: #e8f5e8;
- border: 1px solid #4caf50;
- border-radius: 10px;
- padding: 15px;
- margin-top: 20px;
- }
- .status-display h4 {
- color: #2e7d32;
- margin-bottom: 10px;
- }
- .segment-controls {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 15px;
- margin-top: 15px;
- }
- .segment-item {
- background: white;
- padding: 15px;
- border-radius: 8px;
- border: 1px solid #ddd;
- text-align: center;
- }
- .segment-item h4 {
- color: #333;
- margin-bottom: 10px;
- }
- .loading {
- display: none;
- text-align: center;
- color: #666;
- font-style: italic;
- }
- .error {
- background: #ffebee;
- color: #c62828;
- padding: 10px;
- border-radius: 5px;
- margin: 10px 0;
- display: none;
- }
- .success {
- background: #e8f5e8;
- color: #2e7d32;
- padding: 10px;
- border-radius: 5px;
- margin: 10px 0;
- display: none;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>🎨 LED控制面板</h1>
- <p>通过Web界面控制您的LED灯带与Kodi播放</p>
- </div>
- <div class="content">
- <!-- 展品灯效控制 -->
- <div class="control-group">
- <h3>🎭 展品灯效控制</h3>
- <div style="margin-bottom: 15px;">
- <label for="exhibitId">展品ID (0开始):</label>
- <input type="number" id="exhibitId" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
- </div>
- <div class="button-group">
- <button class="btn btn-primary" onclick="startEffect()">启动灯效</button>
- <button class="btn btn-danger" onclick="stopEffect()">停止灯效</button>
- <button class="btn btn-warning" onclick="getStatus()">刷新状态</button>
- </div>
- </div>
- <!-- Kodi 播放控制 -->
- <div class="control-group" style="border-left-color:#ff6b6b;">
- <h3>🎬 Kodi 播放控制</h3>
- <div style="margin-bottom: 15px;">
- <label for="videoId">视频ID (0开始):</label>
- <input type="number" id="videoId" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
- </div>
- <div class="button-group">
- <button class="btn btn-primary" onclick="startKodi()">开始/切换播放</button>
- <button class="btn btn-warning" onclick="getKodiStatus()">刷新Kodi状态</button>
- </div>
- <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd;">
- <h4 style="color: #666; margin-bottom: 10px; font-size: 1.1em;">🔧 Kodi 系统控制</h4>
- <div class="button-group">
- <button class="btn btn-primary" onclick="revokeIndividualState()">撤销独立状态</button>
- <button class="btn btn-primary" onclick="startAllKodiApps()">启动所有Kodi应用</button>
- </div>
- </div>
- <div class="status-display" id="kodiStatusDisplay" style="margin-top:15px;">
- <h4>📊 Kodi 状态</h4>
- <div id="kodiStatusContent">点击"刷新Kodi状态"查看</div>
- </div>
- </div>
- <!-- 图片播放控制 -->
- <div class="control-group" style="border-left-color:#feca57;">
- <h3>🖼️ 图片播放控制</h3>
- <div style="margin-bottom: 15px;">
- <label for="kodiClientIndexImage">Kodi客户端索引 (0开始):</label>
- <input type="number" id="kodiClientIndexImage" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
- </div>
- <div style="margin-bottom: 15px;">
- <label>方式一:上传图片文件</label>
- <input type="file" id="imageFileInput" accept="image/*" style="margin-top: 10px; padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 5px;">
- </div>
- <div style="margin-bottom: 15px;">
- <label>方式二:输入图片URL</label>
- <input type="text" id="imageUrlInput" placeholder="请输入图片URL,例如: http://example.com/image.jpg" style="margin-top: 10px; padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 5px;">
- </div>
- <div class="button-group">
- <button class="btn btn-primary" onclick="playImage()">播放图片</button>
- </div>
- </div>
- <!-- RTSP视频播放控制 -->
- <div class="control-group" style="border-left-color:#667eea;">
- <h3>📹 RTSP视频播放控制</h3>
- <div style="margin-bottom: 15px;">
- <label for="kodiClientIndexRtsp">Kodi客户端索引 (0开始):</label>
- <input type="number" id="kodiClientIndexRtsp" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
- </div>
- <div style="margin-bottom: 15px;">
- <label for="rtspUrlInput">RTSP视频流URL:</label>
- <input type="text" id="rtspUrlInput" placeholder="请输入RTSP URL,例如: rtsp://example.com/stream" style="margin-top: 10px; padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 5px;">
- </div>
- <div style="margin-bottom: 15px;">
- <label for="rtspVolume">音量 (0-100):</label>
- <input type="number" id="rtspVolume" min="0" max="100" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
- </div>
- <div class="button-group">
- <button class="btn btn-primary" onclick="playRtsp()">播放RTSP流</button>
- </div>
- </div>
- <!-- 灯效说明 -->
- <div class="control-group">
- <h3>📖 灯效说明</h3>
- <div style="background: #f0f8ff; padding: 15px; border-radius: 8px; border-left: 4px solid #4ecdc4;">
- <p><strong>灯效流程:</strong></p>
- <ol style="margin: 10px 0; padding-left: 20px;">
- <li>选择展品ID,点击"启动灯效"</li>
- <li>指定展品将显示白色呼吸灯效,其他展品保持静止</li>
- <li>10秒后,所有展品将随机选择以下灯效之一:</li>
- <ul style="margin: 5px 0; padding-left: 20px;">
- <li>随机波浪效果</li>
- <li>随机闪烁效果</li>
- <li>随机呼吸效果</li>
- </ul>
- <li>所有灯效都使用白色</li>
- </ol>
- </div>
- </div>
- <!-- 状态显示 -->
- <div class="status-display" id="statusDisplay">
- <h4>📊 当前状态</h4>
- <div id="statusContent">点击"刷新状态"查看当前LED状态</div>
- </div>
- <!-- 消息显示 -->
- <div class="error" id="errorMessage"></div>
- <div class="success" id="successMessage"></div>
- <div class="loading" id="loadingMessage">正在处理请求...</div>
- </div>
- </div>
- <script>
- // 全局变量
- let currentStatus = null;
- let currentKodiStatus = null;
- // 页面加载完成后初始化
- document.addEventListener('DOMContentLoaded', function() {
- getStatus();
- getKodiStatus();
- });
- // 显示消息
- function showMessage(message, type = 'success') {
- const errorDiv = document.getElementById('errorMessage');
- const successDiv = document.getElementById('successMessage');
-
- errorDiv.style.display = 'none';
- successDiv.style.display = 'none';
-
- if (type === 'error') {
- errorDiv.textContent = message;
- errorDiv.style.display = 'block';
- } else {
- successDiv.textContent = message;
- successDiv.style.display = 'block';
- }
-
- setTimeout(() => {
- errorDiv.style.display = 'none';
- successDiv.style.display = 'none';
- }, 3000);
- }
- // 显示加载状态
- function showLoading(show = true) {
- document.getElementById('loadingMessage').style.display = show ? 'block' : 'none';
- }
- // 获取LED状态
- async function getStatus() {
- try {
- showLoading(true);
- const response = await fetch('/api/led/status');
- const result = await response.json();
-
- if (result.success) {
- currentStatus = result.data;
- updateUI();
- showMessage('LED状态已刷新');
- } else {
- showMessage('获取LED状态失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // 更新UI显示
- function updateUI() {
- if (!currentStatus) return;
- const statusContent = document.getElementById('statusContent');
- statusContent.innerHTML = `
- <p><strong>运行状态:</strong> ${currentStatus.is_running ? '运行中' : '已停止'}</p>
- <p><strong>状态信息:</strong> ${currentStatus.message}</p>
- `;
- }
- // 启动灯效
- async function startEffect() {
- const exhibitId = parseInt(document.getElementById('exhibitId').value);
- if (isNaN(exhibitId) || exhibitId < 0) {
- showMessage('请输入有效的展品ID(大于等于0的整数)', 'error');
- return;
- }
- try {
- showLoading(true);
- const response = await fetch('/api/led/start', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ exhibit_id: exhibitId })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // 停止灯效
- async function stopEffect() {
- try {
- showLoading(true);
- const response = await fetch('/api/led/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== Kodi 控制 =====
- async function getKodiStatus() {
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/status');
- const result = await response.json();
- if (result.success) {
- currentKodiStatus = result.data;
- const el = document.getElementById('kodiStatusContent');
- el.innerHTML = `
- <p><strong>线程状态:</strong> ${currentKodiStatus.is_running ? '运行中' : '已停止'}</p>
- <p><strong>状态信息:</strong> ${currentKodiStatus.message}</p>
- `;
- showMessage('Kodi状态已刷新');
- } else {
- showMessage('获取Kodi状态失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function startKodi() {
- const videoId = parseInt(document.getElementById('videoId').value);
- if (isNaN(videoId) || videoId < 0) {
- showMessage('请输入有效的视频ID(大于等于0的整数)', 'error');
- return;
- }
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/start', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ video_id: videoId })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== 图片播放控制 =====
- async function playImage() {
- const clientIndex = parseInt(document.getElementById('kodiClientIndexImage').value);
- if (isNaN(clientIndex) || clientIndex < 0) {
- showMessage('请输入有效的客户端索引(大于等于0的整数)', 'error');
- return;
- }
- const fileInput = document.getElementById('imageFileInput');
- const urlInput = document.getElementById('imageUrlInput').value.trim();
- // 检查是否选择了文件或输入了URL
- if (!fileInput.files || fileInput.files.length === 0) {
- if (!urlInput) {
- showMessage('请选择图片文件或输入图片URL', 'error');
- return;
- }
- }
- try {
- showLoading(true);
- let response;
-
- // 如果上传了文件,使用FormData
- if (fileInput.files && fileInput.files.length > 0) {
- const formData = new FormData();
- formData.append('file', fileInput.files[0]);
- formData.append('kodi_client_index', clientIndex);
-
- response = await fetch('/api/kodi/play_image', {
- method: 'POST',
- body: formData
- });
- } else {
- // 使用图片URL
- response = await fetch('/api/kodi/play_image', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- image_url: urlInput,
- kodi_client_index: clientIndex
- })
- });
- }
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== RTSP播放控制 =====
- async function playRtsp() {
- const clientIndex = parseInt(document.getElementById('kodiClientIndexRtsp').value);
- const rtspUrl = document.getElementById('rtspUrlInput').value.trim();
- const volume = parseInt(document.getElementById('rtspVolume').value);
- if (isNaN(clientIndex) || clientIndex < 0) {
- showMessage('请输入有效的客户端索引(大于等于0的整数)', 'error');
- return;
- }
- if (!rtspUrl) {
- showMessage('请输入RTSP视频流URL', 'error');
- return;
- }
- if (isNaN(volume) || volume < 0 || volume > 100) {
- showMessage('音量必须是0-100之间的整数', 'error');
- return;
- }
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/play_rtsp', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- rtsp_url: rtspUrl,
- kodi_client_index: clientIndex,
- volume: volume
- })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== Kodi 系统控制 =====
- async function revokeIndividualState() {
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/revoke_individual_state', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function startAllKodiApps() {
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/start_all_apps', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage('操作失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- </script>
- </body>
- </html>
|