移动端适配:让WebRTC在手机上也能跑得溜

说实话,做WebRTC开发最头疼的往往不是功能本身,而是移动端适配。我刚开始做这个项目时,在PC上调试得好好的,一上手机就各种翻车——黑屏、卡顿、触摸没反应。嗯,今天就把这些坑和经验都倒出来,希望能帮你少走弯路。

移动端浏览器兼容性

先说说浏览器的事。移动端的浏览器生态比PC复杂得多,你想想看,光是系统就有iOS和Android两大阵营,每个系统还有不同的浏览器内核。

核心要点:WebRTC在移动端的支持情况,说白了就是看浏览器内核。iOS上所有浏览器都用的是Safari的WebKit内核,Android上则是Chrome的Blink内核占主导。

我在项目中遇到过最典型的问题:用户用微信内置浏览器打开页面,结果getUserMedia直接返回undefined。为什么会这样?因为微信内置浏览器早期版本对WebRTC支持不完整。

这里我建议你做个特性检测,别假设所有浏览器都支持:

// 检测WebRTC支持情况
function checkWebRTCSupport() {
  const support = {
    getUserMedia: !!(
      navigator.mediaDevices && 
      navigator.mediaDevices.getUserMedia
    ),
    RTCPeerConnection: !!(
      window.RTCPeerConnection || 
      window.webkitRTCPeerConnection
    ),
    RTCDataChannel: !!window.RTCDataChannel
  };
  
  // 输出检测结果
  console.table(support);
  
  // 如果不支持,给出友好提示
  if (!support.getUserMedia) {
    alert('您的浏览器不支持摄像头/麦克风,请使用最新版Chrome或Safari');
  }
  
  return support;
}

注意:iOS 14.3以下版本对WebRTC的支持有严重bug,特别是视频编码器协商这块。我曾经因为这个排查了整整两天,最后发现是系统版本问题。

触摸事件处理

移动端没有鼠标,只有手指。这个区别看似简单,但影响很大。鼠标有hover、click、mousedown、mouseup,触摸只有touchstart、touchmove、touchend。

我个人的习惯是:能用click就别用touch事件。为什么?因为click在移动端有300ms延迟(现在大部分浏览器已经消除了),但touch事件会带来另一个问题——滚动穿透。

举个例子,你在录制视频时想弹出一个控制面板,手指滑动时页面也跟着滚动了。这就是滚动穿透。解决办法其实不复杂:

// 阻止触摸事件的默认行为
function preventScroll(element) {
  element.addEventListener('touchmove', function(e) {
    e.preventDefault();
  }, { passive: false });
}

// 更好的做法:只在需要时阻止
function toggleScrollLock(lock) {
  if (lock) {
    document.body.style.overflow = 'hidden';
    document.body.style.position = 'fixed';
  } else {
    document.body.style.overflow = '';
    document.body.style.position = '';
  }
}

小技巧:处理录制按钮的点击时,我建议同时监听touchstart和click,但用标志位防止重复触发。说白了就是:手指按下时开始录制,手指抬起时结束录制,但别触发两次。

移动端屏幕适配

屏幕适配这块,我踩过最大的坑就是视频画面的比例问题。PC上16:9的摄像头画面,到了手机上可能变成4:3,甚至有些设备是竖屏的9:16。

你想想看,如果直接把视频流塞进一个固定尺寸的容器里,画面要么被拉伸变形,要么被裁剪掉一部分。我建议用CSS的object-fit属性:

/* 视频容器样式 */
.video-container {
  width: 100%;
  height: 0;
  padding-bottom: 56.25%; /* 16:9比例 */
  position: relative;
  overflow: hidden;
  background: #000;
}

.video-container video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: contain; /* 保持比例,显示完整画面 */
  /* 或者用 cover:裁剪填充,不留黑边 */
}

还有一个容易被忽略的点:设备像素比(DPR)。iPhone的DPR是3,很多Android设备是2.5或2。这意味着你设置的100px在屏幕上实际是300物理像素。处理Canvas截图时,如果不考虑DPR,截出来的图会模糊。

// 获取设备像素比
const dpr = window.devicePixelRatio || 1;

// 设置Canvas尺寸时乘以DPR
function setupCanvas(canvas, width, height) {
  canvas.width = width * dpr;
  canvas.height = height * dpr;
  canvas.style.width = width + 'px';
  canvas.style.height = height + 'px';
  
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
  return ctx;
}

移动端性能优化

性能优化这块,说白了就是跟手机的电量和发热做斗争。WebRTC本身就很吃资源,再加上视频编解码,手机发烫是常有的事。

我总结了几条实战经验:

  • 降低分辨率:移动端不需要1080p,720p甚至480p就够用。分辨率降低一半,计算量减少四分之三。
  • 控制帧率:15fps对大多数场景都够用,30fps会明显增加功耗。
  • 适时释放资源:录制完成后一定要调用MediaStream的stop()方法,不然摄像头一直亮着。
// 设置低功耗模式
const constraints = {
  video: {
    width: { ideal: 720 },
    height: { ideal: 1280 },
    frameRate: { ideal: 15, max: 20 },
    facingMode: 'user'
  },
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    sampleRate: 16000 // 降低音频采样率
  }
};

// 记得释放资源
function cleanupMediaStream(stream) {
  if (stream) {
    stream.getTracks().forEach(track => {
      track.stop();
      console.log(`已释放: ${track.kind} track`);
    });
  }
}

警告:在Android低端机上,同时开启摄像头和屏幕共享会导致内存溢出。我曾经在红米Note上测试,直接闪退。解决方案是:检测设备性能,动态调整分辨率。

知识体系总览

下面这张图是我整理的移动端适配核心逻辑,你可以对照着检查自己的实现:

移动端WebRTC适配核心逻辑 移动端适配 浏览器兼容性 触摸事件处理 屏幕适配 性能优化 特性检测 降级方案 用户提示 滚动穿透 事件防抖 手势识别 比例控制 DPR适配 横竖屏切换 分辨率控制 帧率限制 资源释放

这张图把移动端适配的四个核心维度串起来了。你从中心开始,沿着四个方向展开,每个分支下面还有具体的落地措施。我个人习惯在做项目前先画这么一张图,把要处理的问题都列清楚,避免遗漏。

最后说一句:移动端适配没有银弹,最好的办法就是多拿几台真机测试。我自己的测试机柜里常年放着5台不同品牌的手机,每次发版前都要跑一遍。虽然麻烦,但总比用户反馈bug强。

公众号:蓝海资料掘金营,微信deep3321