前端通过后端给的webrtc的链接,在前端展示,并更新实时状态
最近接了一个需求,后端给一个webrtc的流的地址,然后前端展示出来,还需要实时的更新状态,状态这个好更新,后端给了一个接口,只要成功就对了,但是这个webrtc怎么展示,不太会,查看文档,新的api,RTCPeerConnection,浏览器自带的,通过new创建,然后展示到video元素上,具体的注释代码都有,直接复制粘贴即可使用,希望能帮助到大家。
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebRTC流媒体服务器监控</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><style>:root {--primary: #1a2035;--secondary: #252b42;--accent: #4a6cf7;--success: #2ecc71;--warning: #f39c12;--danger: #e74c3c;--text: #e4e8f0;--card-bg: rgba(37, 43, 66, 0.8);}* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, var(--primary), #0d1220);color: var(--text);min-height: 100vh;padding: 20px;}.container {max-width: 1400px;margin: 0 auto;display: grid;grid-template-columns: 1fr 1fr;gap: 20px;}header {grid-column: 1 / -1;display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;padding: 15px 20px;background: var(--card-bg);border-radius: 10px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);}h1 {font-size: 2.2rem;display: flex;align-items: center;gap: 15px;}h1 i {color: var(--accent);}.server-address {background: rgba(0, 0, 0, 0.3);padding: 8px 15px;border-radius: 5px;font-family: monospace;font-size: 0.9rem;}.status-bar {display: flex;gap: 15px;}.status-indicator {display: flex;align-items: center;gap: 5px;padding: 5px 10px;background: rgba(0, 0, 0, 0.3);border-radius: 20px;}.status-dot {width: 10px;height: 10px;border-radius: 50%;}.status-active {background: var(--success);}.status-warning {background: var(--warning);}.status-inactive {background: var(--danger);}.dashboard {display: flex;flex-direction: column;gap: 20px;}.card {background: var(--card-bg);border-radius: 10px;padding: 20px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);}.card-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 15px;padding-bottom: 10px;border-bottom: 1px solid rgba(255, 255, 255, 0.1);}.card-title {font-size: 1.2rem;display: flex;align-items: center;gap: 10px;}.metric {display: flex;justify-content: space-between;margin-bottom: 10px;padding: 8px 0;border-bottom: 1px dashed rgba(255, 255, 255, 0.1);}.metric-value {font-weight: bold;color: var(--accent);}.video-container {position: relative;width: 100%;background: #000;border-radius: 10px;overflow: hidden;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);min-height: 400px;display: flex;align-items: center;justify-content: center;}#video {width: 100%;max-height: 70vh;background: #000;}.video-placeholder {position: absolute;color: #555;text-align: center;z-index: 1;padding: 20px;}.video-placeholder i {font-size: 4rem;margin-bottom: 20px;color: #333;}.controls {display: flex;gap: 10px;margin-top: 15px;}button {background: var(--secondary);color: var(--text);border: none;padding: 12px 20px;border-radius: 5px;cursor: pointer;display: flex;align-items: center;gap: 8px;transition: all 0.3s;flex: 1;justify-content: center;}button:hover {background: var(--accent);transform: translateY(-2px);}.btn-connect {background: var(--success);}.btn-disconnect {background: var(--danger);}.stream-info {margin-top: 15px;padding: 15px;background: rgba(0, 0, 0, 0.2);border-radius: 5px;}.info-line {display: flex;justify-content: space-between;margin-bottom: 8px;}.info-label {color: #aaa;}.info-value {font-weight: bold;}.loading {display: flex;justify-content: center;align-items: center;flex-direction: column;gap: 20px;padding: 40px;}.spinner {width: 50px;height: 50px;border: 5px solid rgba(255, 255, 255, 0.1);border-radius: 50%;border-top-color: var(--accent);animation: spin 1s linear infinite;}@keyframes spin {to {transform: rotate(360deg);}}.api-response {margin-top: 20px;padding: 15px;background: rgba(0, 0, 0, 0.2);border-radius: 5px;max-height: 200px;overflow-y: auto;font-family: monospace;font-size: 0.85rem;}.error-box {background: rgba(231, 76, 60, 0.2);border-left: 4px solid var(--danger);padding: 10px 15px;margin: 10px 0;border-radius: 4px;}.debug-info {margin-top: 20px;padding: 15px;background: rgba(0, 0, 0, 0.2);border-radius: 5px;font-size: 0.9rem;}.debug-title {font-weight: bold;margin-bottom: 10px;display: flex;align-items: center;gap: 8px;}.solution-list {margin: 10px 0;padding-left: 20px;}.solution-list li {margin-bottom: 8px;}.stat-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 15px;margin-top: 15px;}.stat-item {background: rgba(0, 0, 0, 0.2);padding: 15px;border-radius: 8px;text-align: center;}.stat-value {font-size: 1.8rem;font-weight: bold;color: var(--accent);margin: 10px 0;}.stat-label {font-size: 0.9rem;color: #aaa;}@media (max-width: 1024px) {.container {grid-template-columns: 1fr;}header {flex-direction: column;gap: 15px;text-align: center;}.status-bar {justify-content: center;}}@media (max-width: 768px) {h1 {font-size: 1.8rem;}.status-bar {flex-wrap: wrap;justify-content: center;}.controls {flex-direction: column;}.stat-grid {grid-template-columns: 1fr 1fr;}}</style>
</head><body><div class="container"><header><h1><i class="fas fa-broadcast-tower"></i> WebRTC流媒体服务器监控</h1><div class="server-address"><i class="fas fa-link"></i> API: 自己的接口拿到的状态api</div><div class="status-bar"><div class="status-indicator"><div class="status-dot" id="api-status"></div><span>API连接</span></div><div class="status-indicator"><div class="status-dot" id="stream-status-indicator"></div><span>流服务</span></div><div class="status-indicator"><div class="status-dot" id="webrtc-status"></div><span>WebRTC</span></div></div></header><div class="dashboard"><div class="card"><div class="card-header"><div class="card-title"><i class="fas fa-microchip"></i> 服务器状态</div></div><div class="stat-grid"><div class="stat-item"><i class="fas fa-microchip"></i><div class="stat-value" id="server-pid">-</div><div class="stat-label">进程ID</div></div><div class="stat-item"><i class="fas fa-server"></i><div class="stat-value" id="server-name">-</div><div class="stat-label">服务器名称</div></div><div class="stat-item"><i class="fas fa-cogs"></i><div class="stat-value" id="server-service">-</div><div class="stat-label">服务</div></div><div class="stat-item"><i class="fas fa-code"></i><div class="stat-value" id="server-code">-</div><div class="stat-label">状态码</div></div></div></div><div class="card"><div class="card-header"><div class="card-title"><i class="fas fa-info-circle"></i> API信息</div></div><div class="metric"><span>版本信息:</span><span class="metric-value" id="version">接口信息</span></div><div class="metric"><span>系统摘要:</span><span class="metric-value" id="summaries">接口信息</span></div><div class="metric"><span>流管理:</span><span class="metric-value" id="streams">接口信息</span></div><div class="metric"><span>客户端管理:</span><span class="metric-value" id="clients">接口信息</span></div></div><div class="card"><div class="card-header"><div class="card-title"><i class="fas fa-network-wired"></i> 系统功能</div></div><div class="metric"><span>性能统计:</span><span class="metric-value" id="perf">接口信息</span></div><div class="metric"><span>内存信息:</span><span class="metric-value" id="meminfos">接口信息</span></div><div class="metric"><span>虚拟主机:</span><span class="metric-value" id="vhosts">接口信息</span></div><div class="metric"><span>功能特性:</span><span class="metric-value" id="features">接口信息</span></div></div><div class="card"><div class="card-header"><div class="card-title"><i class="fas fa-code"></i> 原始API响应</div></div><div class="api-response" id="api-response">// API响应数据将显示在这里</div></div></div><div class="card"><div class="card-header"><div class="card-title"><i class="fas fa-video"></i> WebRTC流播放器</div></div><div class="video-container"><video id="video" autoplay playsinline muted></video><div class="video-placeholder" id="video-placeholder"><i class="fas fa-satellite-dish"></i><p>等待连接WebRTC流</p><p>WHEP URL: webrtc的流,后端给的链接</p></div></div><div class="controls"><button class="btn-connect" id="connect-btn"><i class="fas fa-plug"></i> 连接流</button><button class="btn-disconnect" id="disconnect-btn" disabled><i class="fas fa-ban"></i> 断开连接</button><button id="refresh-btn"><i class="fas fa-sync-alt"></i> 刷新状态</button></div><div class="stream-info"><div class="info-line"><span class="info-label">流状态:</span><span class="info-value" id="stream-status">未连接</span></div><div class="info-line"><span class="info-label">连接方式:</span><span class="info-value" id="connection-type">-</span></div><div class="info-line"><span class="info-label">ICE状态:</span><span class="info-value" id="ice-state">-</span></div><div class="info-line"><span class="info-label">信令状态:</span><span class="info-value" id="signaling-state">-</span></div></div><div class="debug-info"><div class="debug-title"><i class="fas fa-bug"></i> 连接问题诊断</div><p>如果您遇到断开后重新连接视频不显示的问题,请尝试:</p><ol class="solution-list"><li>完全刷新页面(Ctrl+F5)清除WebRTC状态</li><li>检查浏览器控制台是否有错误信息</li><li>确认服务器端流是否仍在运行</li><li>尝试使用Chrome浏览器,它对WebRTC支持最好</li></ol><div class="error-box" id="error-info" style="display: none;"><strong>错误详情:</strong> <span id="error-message"></span></div></div></div></div><script>document.addEventListener('DOMContentLoaded', function () {const connectBtn = document.getElementById('connect-btn');const disconnectBtn = document.getElementById('disconnect-btn');const refreshBtn = document.getElementById('refresh-btn');const videoElement = document.getElementById('video');const videoPlaceholder = document.getElementById('video-placeholder');const streamStatus = document.getElementById('stream-status');const apiResponseElement = document.getElementById('api-response');const connectionType = document.getElementById('connection-type');const iceState = document.getElementById('ice-state');const signalingState = document.getElementById('signaling-state');const errorInfo = document.getElementById('error-info');const errorMessage = document.getElementById('error-message');const apiStatus = document.getElementById('api-status');const streamStatusIndicator = document.getElementById('stream-status-indicator');const webrtcStatus = document.getElementById('webrtc-status');let pc = null; // WebRTC对等连接对象let streamConnected = false;let apiData = null;// API端点const API_BASE_URL = '状态的api';const WHEP_URL = 'webrtc的流';// 显示错误信息function showError(message) {errorMessage.textContent = message;errorInfo.style.display = 'block';console.error('Error:', message);}// 隐藏错误信息function hideError() {errorInfo.style.display = 'none';}// 更新状态指示器function updateStatusIndicators() {// API连接状态if (apiData) {apiStatus.className = 'status-dot status-active';} else {apiStatus.className = 'status-dot status-inactive';}// 流服务状态if (apiData && apiData.code === 0) {streamStatusIndicator.className = 'status-dot status-active';} else {streamStatusIndicator.className = 'status-dot status-inactive';}// WebRTC状态if (streamConnected) {webrtcStatus.className = 'status-dot status-active';} else {webrtcStatus.className = 'status-dot status-inactive';}}// 从API获取数据async function fetchData() {try {hideError();const response = await fetch(API_BASE_URL, {method: 'GET',mode: 'cors',headers: {'Accept': 'application/json',}});if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}apiData = await response.json();updateUI(apiData);updateStatusIndicators();return true;} catch (error) {console.error('获取API数据失败:', error);showError('获取API数据失败: ' + error.message);apiData = null;updateStatusIndicators();return false;}}// 更新UI显示function updateUI(data) {// 显示原始API响应apiResponseElement.textContent = JSON.stringify(data, null, 2);// 更新服务器信息document.getElementById('server-pid').textContent = data.pid || '-';document.getElementById('server-name').textContent = data.server || '-';document.getElementById('server-service').textContent = data.service || '-';document.getElementById('server-code').textContent = data.code || '-';}// 完全重置WebRTC连接function resetWebRTC() {if (pc) {// 关闭所有轨道if (videoElement.srcObject) {videoElement.srcObject.getTracks().forEach(track => {track.stop();});}// 关闭连接pc.close();pc = null;}// 重置视频元素videoElement.srcObject = null;videoElement.removeAttribute('src');videoElement.load();// 重置状态streamConnected = false;iceState.textContent = '-';signalingState.textContent = '-';console.log('WebRTC连接已完全重置');}// 连接WebRTC流async function connectStream() {if (streamConnected) return;streamStatus.textContent = '连接中...';videoPlaceholder.style.display = 'flex';videoPlaceholder.innerHTML = '<div class="loading"><div class="spinner"></div><p>正在连接WebRTC流...</p></div>';hideError();try {// 先完全重置之前的连接resetWebRTC();// 1. 创建新的RTCPeerConnection(配置STUN服务器)pc = new RTCPeerConnection({iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]});// 2. 监听视频轨道并添加到video元素pc.ontrack = (event) => {console.log('收到轨道:', event.track.kind);if (event.track.kind === 'video') {videoElement.srcObject = event.streams[0];streamConnected = true;videoPlaceholder.style.display = 'none';videoElement.style.display = 'block';streamStatus.textContent = '已连接';connectionType.textContent = 'WebRTC (WHEP)';connectBtn.disabled = true;disconnectBtn.disabled = false;updateStatusIndicators();console.log('视频轨道已添加,播放开始');}};// 监听连接状态变化pc.oniceconnectionstatechange = () => {iceState.textContent = pc.iceConnectionState;console.log('ICE连接状态:', pc.iceConnectionState);};pc.onsignalingstatechange = () => {signalingState.textContent = pc.signalingState;console.log('信令状态:', pc.signalingState);};pc.onconnectionstatechange = () => {console.log('连接状态:', pc.connectionState);};// 3. 创建Offer并发送给WHEP服务器const offer = await pc.createOffer({ offerToReceiveVideo: true });await pc.setLocalDescription(offer);console.log('已创建本地SDP Offer');// 4. 将SDP Offer通过HTTP POST发送到WHEP端点const response = await fetch(WHEP_URL, {method: 'POST',body: pc.localDescription.sdp,headers: { 'Content-Type': 'application/sdp' }});if (!response.ok) {const errorText = await response.text();throw new Error(`WHEP错误! 状态: ${response.status}, 响应: ${errorText}`);}const sdpAnswer = await response.text();console.log('已收到SDP Answer');// 5. 将服务器返回的SDP Answer设置为远程描述await pc.setRemoteDescription({ type: 'answer', sdp: sdpAnswer });console.log('WebRTC连接已建立');} catch (error) {console.error('连接错误:', error);streamStatus.textContent = '连接失败';showError('连接失败: ' + error.message);videoPlaceholder.innerHTML = `<i class="fas fa-exclamation-triangle"></i><p>连接失败: ${error.message}</p>`;// 重置连接状态resetWebRTC();connectBtn.disabled = false;disconnectBtn.disabled = true;updateStatusIndicators();}}// 断开WebRTC流function disconnectStream() {if (!streamConnected) return;// 完全重置WebRTC连接resetWebRTC();// 隐藏视频,显示占位符videoElement.style.display = 'none';videoPlaceholder.style.display = 'flex';videoPlaceholder.innerHTML = `<i class="fas fa-satellite-dish"></i><p>等待连接WebRTC流</p>`;streamStatus.textContent = '未连接';connectionType.textContent = '-';connectBtn.disabled = false;disconnectBtn.disabled = true;updateStatusIndicators();console.log('WebRTC连接已断开');}// 刷新服务器状态async function refreshStats() {refreshBtn.innerHTML = '<i class="fas fa-sync-alt fa-spin"></i> 刷新中...';refreshBtn.disabled = true;const success = await fetchData();refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 刷新状态';refreshBtn.disabled = false;if (!success) {showError('获取服务器状态失败,请检查API地址和网络连接');}}// 添加事件监听器connectBtn.addEventListener('click', connectStream);disconnectBtn.addEventListener('click', disconnectStream);refreshBtn.addEventListener('click', refreshStats);// 初始化refreshStats();});</script>
</body></html>