You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebRTC远程端音频无法播放问题排查求助

解决WebRTC P2P音频无法互通的问题

我帮你排查了代码里的几个关键问题,这些应该就是导致双方听不到对方音频的核心原因,下面是具体的修复方案和完整修正代码:

核心问题分析

  • ICE服务器配置未生效:你定义了pcConfig但创建RTCPeerConnection时传入了null,这会导致无法获取公网ICE候选,跨网络情况下根本无法建立连接。
  • ICE候选处理不规范:发送候选时传递了sdpMid,但接收时没正确对应,而且缺少错误捕获,候选添加失败也没办法排查。
  • Answer方法缺少错误处理:createAnswer没有catch块,出错了完全没日志,很难定位问题。
  • Socket房间事件逻辑有漏洞:初始用户和加入用户的isChannelReady设置时机不对,可能导致媒体流还没准备好就尝试建立连接。
  • 服务器端代码不完整:你的index.js没写完,Socket.io的房间转发逻辑缺失,消息根本传不到对方那里。

分步修复方案

1. 启用ICE服务器配置

修改createPeerConnection函数,把定义好的pcConfig传进去:

function createPeerConnection() {
  pc = new RTCPeerConnection(pcConfig); // 替换原来的null
  pc.onicecandidate = function (e) {
    if (e.candidate) {
      sendMessage({
        type: 'candidate',
        label: e.candidate.sdpMLineIndex,
        id: e.candidate.sdpMid,
        candidate: e.candidate.candidate
      });
    }
  }
  pc.ontrack = function (e) {
    console.log('Remote track added', e);
    remoteStream = e.streams[0];
    remoteVideo.srcObject = remoteStream;
  }
}

2. 规范ICE候选的接收逻辑

接收候选时要正确对应sdpMid,并且添加错误捕获:

socket.on('message', function (message) {
  // ... 其他逻辑不变
  else if (message.type === 'candidate' && isStarted) {
    const candidate = new RTCIceCandidate({
      sdpMLineIndex: message.label,
      sdpMid: message.id, // 对应发送时的id字段
      candidate: message.candidate
    });
    pc.addIceCandidate(candidate).catch(err => console.error('添加ICE候选失败:', err));
    console.log('Candidate added successfully');
  }
});

3. 为Answer方法添加错误捕获

function answer() {
  pc.createAnswer(null)
    .then((sessionDescription) => {
      pc.setLocalDescription(sessionDescription);
      sendMessage(sessionDescription);
    })
    .catch(e => console.error('创建Answer失败:', e));
}

4. 修正Socket房间事件逻辑

确保双方的isChannelReady都能正确触发,并且加入用户后主动通知对方:

socket.on('created', function (room) {
  isInitializer = true;
  console.log('Created room ' + room);
});

socket.on('join', function (room) {
  console.log('Another peer joined room ' + room);
  isChannelReady = true; // 初始用户标记通道就绪
});

socket.on('joined', function (room) {
  console.log('Successfully joined room ' + room);
  isChannelReady = true; // 加入用户标记通道就绪
  sendMessage('got user media'); // 通知对方自己已准备好媒体流
});

5. 补全服务器端Socket.io逻辑

你的index.js没写完,下面是完整的服务器代码,确保消息能正确转发到房间内的其他用户:

const express = require('express');
const path = require('path');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);

app.use(express.static(path.resolve(__dirname, '../public')));
app.get('*', (req, res) => {
  res.sendFile(path.resolve(__dirname, '../public/index.html'));
});

const port = process.env.PORT || 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

// 处理Socket.io房间逻辑
io.on('connection', (socket) => {
  socket.on('create or join', (room) => {
    const clientsInRoom = io.sockets.adapter.rooms.get(room);
    const numClients = clientsInRoom ? clientsInRoom.size : 0;

    if (numClients === 0) {
      socket.join(room);
      socket.emit('created', room);
    } else if (numClients === 1) {
      socket.join(room);
      socket.emit('joined', room);
      // 通知房间内已有用户新用户加入
      socket.to(room).emit('join', room);
    } else {
      socket.emit('full', room);
    }
  });

  // 转发消息到房间内其他用户
  socket.on('message', (message) => {
    // 获取当前socket所在的房间(这里假设用户只在一个房间)
    const room = [...socket.rooms][1]; // 第一个是socket自身的id,第二个是房间名
    socket.to(room).emit('message', message);
  });
});

验证步骤

  1. 测试前确保浏览器允许麦克风权限,查看浏览器标签栏的麦克风图标是否亮起,确认本地媒体流已正确获取。
  2. 打开浏览器控制台,检查是否有Offer、Answer、Candidate added successfully的日志,没有报错信息。
  3. 如果是跨网络测试,确保stun:stun.l.google.com:19302可以正常访问(这个是公开STUN服务器,一般都可用)。

内容的提问来源于stack exchange,提问作者user8868343

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:27:43