前端通过后端给的webrtc的链接,在前端展示,并更新实时状态

发布于:2025-09-09 ⋅ 阅读:(19) ⋅ 点赞:(0)

最近接了一个需求,后端给一个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>

网站公告

今日签到

点亮在社区的每一天
去签到