移动端适配:让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上测试,直接闪退。解决方案是:检测设备性能,动态调整分辨率。
知识体系总览
下面这张图是我整理的移动端适配核心逻辑,你可以对照着检查自己的实现:
这张图把移动端适配的四个核心维度串起来了。你从中心开始,沿着四个方向展开,每个分支下面还有具体的落地措施。我个人习惯在做项目前先画这么一张图,把要处理的问题都列清楚,避免遗漏。
最后说一句:移动端适配没有银弹,最好的办法就是多拿几台真机测试。我自己的测试机柜里常年放着5台不同品牌的手机,每次发版前都要跑一遍。虽然麻烦,但总比用户反馈bug强。