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);
    });
  });
}

横竖屏适配:别让画面歪了

这是移动端屏幕共享最容易被忽略的问题。

用户可能竖着拿着手机共享,也可能横着。接收端可能是桌面浏览器,也可能是另一个手机。画面方向不对,体验直接归零。

核心问题:屏幕方向变化时,视频轨道需要重新协商

当手机从竖屏切换到横屏时,视频的宽高比会变化。如果你不处理,接收端看到的画面要么被拉伸,要么出现黑边。

我记得有一次演示,客户把手机横过来,结果共享画面变成了「竖屏内容+左右大黑边」,看起来特别业余。

正确的做法:

  1. 监听orientationchange事件
  2. 获取新的屏幕尺寸
  3. 调用getDisplayMedia重新获取流(或者调整track的约束)
  4. 通过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()来调整分辨率。但说实话,这个方法在移动端兼容性一般,我建议还是重新获取流更稳妥。

移动端屏幕共享的整体架构

说了这么多,咱们用一张图来总结移动端屏幕共享的核心流程:

移动端屏幕共享核心流程 检测getDisplayMedia 是否支持? 用户手势触发 点击按钮启动 获取流 MediaStream 降级方案 原生SDK / 录屏推流 触摸交互处理 事件转发 / 键盘适配 横竖屏适配 重新获取流 / 更新编码 推流到远端 WebRTC 虚线表示异步流程,实线表示主流程

避坑指南

最后分享几个我踩过的坑:

  • 帧率别设太高:移动端屏幕共享,15fps就够用了。我曾经设了30fps,结果手机发热严重,电量哗哗掉。
  • 分辨率自适应:不要固定分辨率。根据当前屏幕方向动态调整,竖屏用720x1280,横屏用1280x720。
  • 音频别乱开:移动端屏幕共享时,如果同时采集系统音频和麦克风,会出现回声。我建议默认关闭系统音频,只采集麦克风。
  • 测试要全面:不同品牌、不同系统版本的手机,行为差异很大。华为、小米、三星,我每台都测过,没有一台表现完全一样。

总结一下:移动端屏幕共享,核心就三件事——检测兼容性、处理好触摸交互、搞定横竖屏切换。别想着一步到位,先跑通基础流程,再慢慢优化细节。

嗯,移动端适配这块确实坑多,但只要你把上面这些点都照顾到了,基本不会出大问题。下次遇到移动端屏幕共享的需求,你应该知道从哪下手了吧?