You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RTSPtoWeb/web/templates/api_demo.tmpl

548 lines
17 KiB
Cheetah

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{{template "head.tmpl" .}}
<style>
.camera-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
}
.camera-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.camera-card.online {
border-left: 4px solid #28a745;
}
.camera-card.offline {
border-left: 4px solid #dc3545;
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.status-online {
background: #d4edda;
color: #155724;
}
.status-offline {
background: #f8d7da;
color: #721c24;
}
.api-controls {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.btn-group {
margin: 5px;
}
.loading {
text-align: center;
padding: 40px;
color: #6c757d;
}
.error {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 4px;
margin: 10px 0;
}
.success {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 4px;
margin: 10px 0;
}
.video-preview {
width: 100%;
height: 200px;
background: #000;
border-radius: 4px;
margin: 10px 0;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.api-response {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
margin: 10px 0;
font-family: monospace;
font-size: 12px;
max-height: 300px;
overflow-y: auto;
}
</style>
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0 text-dark">API演示页面</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item active">API演示</li>
</ol>
</div>
</div>
</div>
</div>
<div class="content">
<div class="container-fluid">
<!-- API控制面板 -->
<div class="api-controls">
<h4><i class="fas fa-code"></i> API控制面板</h4>
<p class="text-muted">此页面演示如何通过JavaScript API与RTSPtoWeb服务交互适用于Java项目集成参考。</p>
<div class="row">
<div class="col-md-6">
<h5>摄像头管理</h5>
<div class="btn-group">
<button class="btn btn-primary" onclick="loadCamerasAPI()">获取摄像头列表</button>
<button class="btn btn-info" onclick="refreshCamerasAPI()">刷新状态</button>
<button class="btn btn-success" onclick="showAddCameraForm()">添加摄像头</button>
</div>
</div>
<div class="col-md-6">
<h5>流管理</h5>
<div class="btn-group">
<button class="btn btn-secondary" onclick="loadStreamsAPI()">获取流列表</button>
<button class="btn btn-warning" onclick="clearDisplay()">清空显示</button>
<button class="btn btn-dark" onclick="showAPIResponse()">显示API响应</button>
</div>
</div>
</div>
</div>
<!-- 状态显示 -->
<div id="status-message"></div>
<!-- 摄像头列表 -->
<div id="camera-display">
<div class="loading">
<i class="fas fa-info-circle"></i> 点击上方按钮开始API演示
</div>
</div>
<!-- API响应显示 -->
<div id="api-response-container" style="display: none;">
<h4>API响应数据</h4>
<pre id="api-response" class="api-response"></pre>
</div>
<!-- 添加摄像头表单 -->
<div class="modal fade" id="addCameraModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加摄像头</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<form id="addCameraForm">
<div class="form-group">
<label>摄像头名称</label>
<input type="text" class="form-control" id="cameraName" required>
</div>
<div class="form-group">
<label>IP地址</label>
<input type="text" class="form-control" id="cameraIP" required>
</div>
<div class="form-group">
<label>端口</label>
<input type="number" class="form-control" id="cameraPort" value="554">
</div>
<div class="form-group">
<label>用户名</label>
<input type="text" class="form-control" id="cameraUsername">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" class="form-control" id="cameraPassword">
</div>
<div class="form-group">
<label>RTSP URL</label>
<input type="text" class="form-control" id="cameraRTSP" placeholder="rtsp://username:password@ip:port/stream">
</div>
<div class="form-group">
<label>单位代码</label>
<input type="text" class="form-control" id="cameraUnitCode">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="addCameraAPI()">添加</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// API演示脚本
let lastAPIResponse = null;
// 显示状态消息
function showStatus(message, type = 'info') {
const statusDiv = document.getElementById('status-message');
const alertClass = type === 'error' ? 'error' : (type === 'success' ? 'success' : 'alert alert-info');
statusDiv.innerHTML = `<div class="${alertClass}">${message}</div>`;
// 3秒后自动清除
setTimeout(() => {
statusDiv.innerHTML = '';
}, 3000);
}
// 加载摄像头列表
async function loadCamerasAPI() {
try {
showStatus('正在获取摄像头列表...', 'info');
const response = await fetch('/cameras');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
lastAPIResponse = data;
if (data.cameras && data.cameras.length > 0) {
displayCameras(data.cameras);
showStatus(`成功获取 ${data.cameras.length} 个摄像头`, 'success');
} else {
showEmptyState('暂无摄像头数据');
showStatus('摄像头列表为空', 'info');
}
} catch (error) {
console.error('获取摄像头失败:', error);
showStatus(`获取摄像头失败: ${error.message}`, 'error');
showEmptyState('获取摄像头失败');
}
}
// 加载流列表
async function loadStreamsAPI() {
try {
showStatus('正在获取流列表...', 'info');
const response = await fetch('/streams');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
lastAPIResponse = data;
if (data.status === 1 && data.payload) {
const streams = Object.entries(data.payload).map(([key, value]) => ({
id: key,
...value
}));
displayStreams(streams);
showStatus(`成功获取 ${streams.length} 个视频流`, 'success');
} else {
showEmptyState('暂无流数据');
showStatus('流列表为空', 'info');
}
} catch (error) {
console.error('获取流列表失败:', error);
showStatus(`获取流列表失败: ${error.message}`, 'error');
showEmptyState('获取流列表失败');
}
}
// 刷新摄像头状态
async function refreshCamerasAPI() {
try {
showStatus('正在刷新摄像头状态...', 'info');
const response = await fetch('/cameras/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
lastAPIResponse = data;
showStatus('摄像头状态刷新成功', 'success');
// 重新加载摄像头列表
setTimeout(() => {
loadCamerasAPI();
}, 1000);
} catch (error) {
console.error('刷新摄像头状态失败:', error);
showStatus(`刷新失败: ${error.message}`, 'error');
}
}
// 显示摄像头列表
function displayCameras(cameras) {
const container = document.getElementById('camera-display');
container.innerHTML = '<h4><i class="fas fa-camera"></i> 摄像头列表</h4>';
const grid = document.createElement('div');
grid.className = 'camera-grid';
cameras.forEach(camera => {
const card = createCameraCard(camera);
grid.appendChild(card);
});
container.appendChild(grid);
}
// 显示流列表
function displayStreams(streams) {
const container = document.getElementById('camera-display');
container.innerHTML = '<h4><i class="fas fa-video"></i> 视频流列表</h4>';
const grid = document.createElement('div');
grid.className = 'camera-grid';
streams.forEach(stream => {
const card = createStreamCard(stream);
grid.appendChild(card);
});
container.appendChild(grid);
}
// 创建摄像头卡片
function createCameraCard(camera) {
const card = document.createElement('div');
const statusClass = camera.status === 'online' ? 'online' : 'offline';
const statusBadgeClass = camera.status === 'online' ? 'status-online' : 'status-offline';
card.className = `camera-card ${statusClass}`;
card.innerHTML = `
<h5>${camera.name || camera.camera_name || '未命名摄像头'}</h5>
<div class="mb-2">
<span class="status-badge ${statusBadgeClass}">${camera.status === 'online' ? '在线' : '离线'}</span>
${camera.enabled ? '<span class="badge badge-success ml-1">已启用</span>' : '<span class="badge badge-warning ml-1">已禁用</span>'}
</div>
<div class="video-preview">
<i class="fas fa-video fa-2x"></i>
</div>
<p><strong>IP:</strong> ${camera.ip}</p>
<p><strong>类型:</strong> ${camera.device_type || '网络摄像头'}</p>
${camera.unit_code ? `<p><strong>单位:</strong> ${camera.unit_code}</p>` : ''}
<div class="btn-group-vertical w-100">
<button class="btn btn-sm btn-info" onclick="playCamera('${camera.id || camera.camera_id}', 'hls')">HLS播放</button>
<button class="btn btn-sm btn-success" onclick="playCamera('${camera.id || camera.camera_id}', 'webrtc')">WebRTC播放</button>
<button class="btn btn-sm btn-warning" onclick="getCameraDetails('${camera.id || camera.camera_id}')">查看详情</button>
</div>
`;
return card;
}
// 创建流卡片
function createStreamCard(stream) {
const card = document.createElement('div');
card.className = 'camera-card online';
const channelCount = Object.keys(stream.channels || {}).length;
card.innerHTML = `
<h5>${stream.name || '未命名流'}</h5>
<div class="mb-2">
<span class="status-badge status-online">活跃</span>
<span class="badge badge-info ml-1">${channelCount} 通道</span>
</div>
<div class="video-preview">
<i class="fas fa-play-circle fa-2x"></i>
</div>
<p><strong>流ID:</strong> ${stream.id}</p>
<div class="btn-group-vertical w-100">
<button class="btn btn-sm btn-info" onclick="playStream('${stream.id}', 'hls')">HLS播放</button>
<button class="btn btn-sm btn-success" onclick="playStream('${stream.id}', 'webrtc')">WebRTC播放</button>
<button class="btn btn-sm btn-primary" onclick="playStream('${stream.id}', 'all')">通用播放</button>
</div>
`;
return card;
}
// 播放摄像头
function playCamera(cameraId, type) {
const url = getPlayUrl(cameraId, 0, type);
window.open(url, '_blank');
showStatus(`正在打开 ${type.toUpperCase()} 播放器...`, 'info');
}
// 播放流
function playStream(streamId, type) {
const url = getPlayUrl(streamId, 0, type);
window.open(url, '_blank');
showStatus(`正在打开 ${type.toUpperCase()} 播放器...`, 'info');
}
// 获取播放URL
function getPlayUrl(id, channel = 0, type = 'hls') {
const baseUrl = window.location.origin;
switch (type.toLowerCase()) {
case 'hls':
return `${baseUrl}/stream/${id}/channel/${channel}/hls/live/index.m3u8`;
case 'webrtc':
return `${baseUrl}/pages/player/webrtc/${id}/${channel}`;
case 'mse':
return `${baseUrl}/pages/player/mse/${id}/${channel}`;
default:
return `${baseUrl}/pages/player/all/${id}/${channel}`;
}
}
// 获取摄像头详情
async function getCameraDetails(cameraId) {
try {
const response = await fetch(`/camera/${cameraId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const camera = await response.json();
lastAPIResponse = camera;
alert(`摄像头详情:\n名称: ${camera.name}\nIP: ${camera.ip}\n状态: ${camera.status}\n创建时间: ${camera.created_at}`);
} catch (error) {
showStatus(`获取摄像头详情失败: ${error.message}`, 'error');
}
}
// 显示添加摄像头表单
function showAddCameraForm() {
$('#addCameraModal').modal('show');
}
// 添加摄像头
async function addCameraAPI() {
try {
const formData = {
name: document.getElementById('cameraName').value,
ip: document.getElementById('cameraIP').value,
port: parseInt(document.getElementById('cameraPort').value) || 554,
username: document.getElementById('cameraUsername').value,
password: document.getElementById('cameraPassword').value,
rtsp_url: document.getElementById('cameraRTSP').value,
unit_code: document.getElementById('cameraUnitCode').value,
enabled: true
};
if (!formData.name || !formData.ip) {
showStatus('请填写摄像头名称和IP地址', 'error');
return;
}
const response = await fetch('/camera/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
lastAPIResponse = result;
$('#addCameraModal').modal('hide');
document.getElementById('addCameraForm').reset();
showStatus('摄像头添加成功', 'success');
// 重新加载摄像头列表
setTimeout(() => {
loadCamerasAPI();
}, 1000);
} catch (error) {
showStatus(`添加摄像头失败: ${error.message}`, 'error');
}
}
// 显示空状态
function showEmptyState(message) {
const container = document.getElementById('camera-display');
container.innerHTML = `
<div class="loading">
<i class="fas fa-info-circle"></i> ${message}
</div>
`;
}
// 清空显示
function clearDisplay() {
const container = document.getElementById('camera-display');
container.innerHTML = `
<div class="loading">
<i class="fas fa-info-circle"></i> 显示已清空
</div>
`;
document.getElementById('api-response-container').style.display = 'none';
showStatus('显示已清空', 'info');
}
// 显示API响应
function showAPIResponse() {
if (lastAPIResponse) {
document.getElementById('api-response').textContent = JSON.stringify(lastAPIResponse, null, 2);
document.getElementById('api-response-container').style.display = 'block';
showStatus('API响应数据已显示', 'info');
} else {
showStatus('暂无API响应数据', 'error');
}
}
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
showStatus('API演示页面已加载点击上方按钮开始测试', 'info');
});
</script>
{{template "foot.tmpl" .}}