22、屏幕共享移动端适配:移动端getDisplayMedia支持、触摸交互、横竖屏适配
说实话,做屏幕共享最头疼的不是桌面端,而是移动端。
我在2021年接了个移动端会议项目,客户要求手机也能共享屏幕。当时我心想,这不就是调个getDisplayMedia嘛,有啥难的?结果一测试,iOS直接不支持,Android碎片化严重,触摸交互一塌糊涂,横竖屏切换时画面直接崩掉。
嗯,从那以后我再也不敢小看移动端适配了。
移动端getDisplayMedia的现状
先泼盆冷水:移动端对getDisplayMedia的支持非常有限。
| 平台 | 支持情况 | 备注 |
|---|---|---|
| Android Chrome 107+ | ✅ 支持 | 需用户手动点击触发,不能自动调用 |
| iOS Safari | ❌ 不支持 | 至今未开放getDisplayMedia API |
| iOS 第三方浏览器 | ❌ 不支持 | 受限于WebKit内核限制 |
| Android 微信/QQ内置浏览器 | ❌ 不支持 | X5内核未实现该API |
说白了,移动端屏幕共享目前还是「半残」状态。但项目不能等,怎么办?
核心思路:能原生就原生,不能原生就用WebRTC的屏幕采集降级方案——通过MediaRecorder录制屏幕,再通过DataChannel传输。
我个人习惯的做法是:先检测navigator.mediaDevices.getDisplayMedia是否存在,如果不存在,就引导用户下载原生SDK或者使用录屏+推流的方式。
// 移动端getDisplayMedia兼容检测
async function startMobileScreenShare() {
if (typeof navigator.mediaDevices?.getDisplayMedia !== 'function') {
// iOS或不支持的Android浏览器
console.warn('当前设备不支持getDisplayMedia');
// 降级方案:引导用户使用原生录屏
showNativeScreenShareGuide();
return;
}
try {
// 注意:必须由用户手势触发
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
width: { ideal: 720 },
height: { ideal: 1280 },
frameRate: { ideal: 15 } // 移动端帧率别设太高
},
audio: false
});
return stream;
} catch (err) {
console.error('屏幕共享失败:', err);
// 用户拒绝或出错
}
}
注意:移动端getDisplayMedia必须由用户点击触发,不能放在页面加载时自动调用。我曾经在onload里直接调用,结果Android Chrome直接报错「需要用户手势」。
触摸交互:移动端特有的痛点
桌面端屏幕共享,用户用鼠标操作,一切正常。但移动端不一样——触摸事件和鼠标事件是两码事。
你想想看,用户用手指在手机上滑动、缩放、点击,这些操作在屏幕共享时怎么处理?
问题1:触摸事件被吞掉
当你在移动端启动屏幕共享后,浏览器会弹出一个「正在共享屏幕」的浮层。这个浮层会拦截触摸事件,导致用户无法正常操作页面。
我遇到过的情况是:用户点击「开始共享」后,页面上的按钮就点不动了。排查了半天,发现是浏览器的安全浮层在作怪。
解决方案:
- 在共享开始后,延迟200ms再绑定触摸事件
- 使用
touch-action: manipulation禁用双击缩放 - 避免在共享区域使用复杂的触摸手势
// 触摸事件兼容处理
function handleTouchInteraction() {
// 延迟绑定,避开浏览器浮层
setTimeout(() => {
document.addEventListener('touchstart', (e) => {
// 阻止默认行为,避免页面滚动干扰共享
e.preventDefault();
// 转发触摸坐标到远端
sendTouchEvent({
type: 'touchstart',
x: e.touches[0].clientX,
y: e.touches[0].clientY
});
}, { passive: false });
}, 200);
}
问题2:虚拟键盘弹出导致布局错乱
移动端共享屏幕时,如果用户点击输入框,虚拟键盘弹出来,整个页面会被压缩。共享出去的画面就变得很奇怪——底部一大块黑色区域。
我的做法是:在共享期间,检测到输入框聚焦时,强制页面滚动到可视区域,并锁定视口高度。
// 锁定视口高度,防止键盘弹出导致布局变化
function lockViewportDuringShare() {
const meta = document.querySelector('meta[name=viewport]');
if (meta) {
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
}
// 监听输入框聚焦
document.querySelectorAll('input, textarea').forEach(el => {
el.addEventListener('focus', () => {
// 延迟一下等键盘弹出
setTimeout(() => {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 300);
});
});
}
横竖屏适配:别让画面歪了
这是移动端屏幕共享最容易被忽略的问题。
用户可能竖着拿着手机共享,也可能横着。接收端可能是桌面浏览器,也可能是另一个手机。画面方向不对,体验直接归零。
核心问题:屏幕方向变化时,视频轨道需要重新协商
当手机从竖屏切换到横屏时,视频的宽高比会变化。如果你不处理,接收端看到的画面要么被拉伸,要么出现黑边。
我记得有一次演示,客户把手机横过来,结果共享画面变成了「竖屏内容+左右大黑边」,看起来特别业余。
正确的做法:
- 监听
orientationchange事件 - 获取新的屏幕尺寸
- 调用
getDisplayMedia重新获取流(或者调整track的约束) - 通过
RTCRtpSender.setParameters更新编码参数
// 横竖屏切换处理
function handleOrientationChange(pc) {
window.addEventListener('orientationchange', async () => {
// 等待旋转完成
await new Promise(resolve => setTimeout(resolve, 300));
const width = window.screen.width;
const height = window.screen.height;
// 重新获取屏幕流
const newStream = await navigator.mediaDevices.getDisplayMedia({
video: {
width: { exact: width },
height: { exact: height },
frameRate: { ideal: 15 }
}
});
// 替换轨道
const [newTrack] = newStream.getVideoTracks();
const sender = pc.getSenders().find(s => s.track?.kind === 'video');
if (sender) {
await sender.replaceTrack(newTrack);
}
// 更新编码参数
const params = sender.getParameters();
params.encodings[0].maxBitrate = width * height * 0.1; // 粗略计算
await sender.setParameters(params);
});
}
小技巧:如果不想重新获取流,可以尝试用MediaStreamTrack.applyConstraints()来调整分辨率。但说实话,这个方法在移动端兼容性一般,我建议还是重新获取流更稳妥。
移动端屏幕共享的整体架构
说了这么多,咱们用一张图来总结移动端屏幕共享的核心流程:
避坑指南
最后分享几个我踩过的坑:
- 帧率别设太高:移动端屏幕共享,15fps就够用了。我曾经设了30fps,结果手机发热严重,电量哗哗掉。
- 分辨率自适应:不要固定分辨率。根据当前屏幕方向动态调整,竖屏用720x1280,横屏用1280x720。
- 音频别乱开:移动端屏幕共享时,如果同时采集系统音频和麦克风,会出现回声。我建议默认关闭系统音频,只采集麦克风。
- 测试要全面:不同品牌、不同系统版本的手机,行为差异很大。华为、小米、三星,我每台都测过,没有一台表现完全一样。
总结一下:移动端屏幕共享,核心就三件事——检测兼容性、处理好触摸交互、搞定横竖屏切换。别想着一步到位,先跑通基础流程,再慢慢优化细节。
嗯,移动端适配这块确实坑多,但只要你把上面这些点都照顾到了,基本不会出大问题。下次遇到移动端屏幕共享的需求,你应该知道从哪下手了吧?