You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS PWA中MediaRecorder仅能正常录制一次,如何彻底清理音轨解决该问题?

iOS PWA中MediaRecorder仅能正常录制一次,如何彻底清理音轨解决该问题?

兄弟,我太懂你这个坑了!iOS PWA里的MediaRecorder简直是个磨人的小妖精,我之前也踩过一模一样的坑——Safari浏览器里怎么测都正常,一旦装成桌面PWA,第一次录制没问题,关掉再打开就彻底罢工,非得重启手机才行。结合你的代码来看,问题出在iOS WebKit对PWA的媒体资源回收机制和普通浏览器不一样,你现有的cleanup函数虽然做了基础清理,但时机和彻底性都不够,导致音轨资源被死死占用,下次请求时拿不到新的流。

问题根源拆解

iOS的PWA在后台挂起时(比如你按Home键回到桌面),WebKit不会像Safari那样自动释放媒体资源,而且MediaRecorder的实例和音轨可能会被缓存,哪怕你调用了track.stop(),也可能有残留的引用没被GC回收。加上你的代码里没有监听PWA的后台挂起事件,导致资源一直被占着,下次启动时请求媒体流就会失败。

具体修复方案(结合你的Vue代码修改)

1. 监听PWA后台挂起事件,主动清理资源

iOS PWA被后台挂起时,必须手动触发资源清理,不然媒体流会一直“挂”在那里。在你的<script setup>里添加页面可见性监听:

import { ref, computed, onMounted, onUnmounted } from 'vue' // 别忘了导入onMounted和onUnmounted

// ... 你的其他变量定义 ...

// 监听页面可见性变化,处理PWA后台挂起的情况
onMounted(() => {
  document.addEventListener('visibilitychange', handleVisibilityChange)
})

onUnmounted(() => {
  document.removeEventListener('visibilitychange', handleVisibilityChange)
})

function handleVisibilityChange() {
  if (document.hidden) {
    // 当PWA被后台挂起时,强制停止所有录制并清理资源
    if (captureState.value === 'recording') {
      stopRecording().then(() => {
        cleanupMedia()
      })
    } else if (mediaStream || mediaRecorder) {
      cleanupMedia()
    }
  }
}

2. 彻底优化cleanupMedia函数,确保资源100%释放

你的原cleanup函数漏掉了移除事件监听和更严格的音轨状态检查,修改成这样:

function cleanupMedia () {
  try {
    // 先处理MediaRecorder:停止录制、移除事件监听、置空
    if (mediaRecorder) {
      if (mediaRecorder.state !== 'inactive') {
        mediaRecorder.stop()
      }
      // 移除所有绑定的事件,避免内存泄漏
      mediaRecorder.ondataavailable = null
      mediaRecorder.onstop = null
      mediaRecorder = null
    }
    chunks = [] // 清空录制片段数组
    
    // 处理媒体流:强制停止所有活跃音轨并释放
    if (mediaStream) {
      mediaStream.getTracks().forEach(track => {
        // 只停止活跃状态的音轨
        if (track.readyState === 'live') {
          track.stop()
        }
        // 移除音轨的事件监听
        track.onended = null
        track.onmute = null
        track.onunmute = null
      })
      mediaStream = null
    }
  } catch (err) {
    console.warn('清理媒体资源时出错:', err)
  }
}

3. 在启动录制前,先清理残留资源

startRecording函数的最开头,主动调用一次cleanupMedia,确保没有旧的资源残留:

async function startRecording () {
  errorMessage.value = ''
  try {
    // 先清理可能残留的媒体资源,双重保险
    cleanupMedia()
    
    if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
      throw new Error('Audio recording requires HTTPS')
    }
    if (!navigator.mediaDevices?.getUserMedia) {
      throw new Error('Audio recording not supported in this browser')
    }
    // ... 你原有的获取媒体流和启动录制的代码 ...
  } catch (err) {
    errorMessage.value = err?.message || String(err)
    cleanupMedia()
    captureState.value = 'idle'
  }
}

4. 优化stopRecording函数,确保录制完全停止后再清理

修改stopRecording,等待录制真正停止后再清理资源,避免异步时序问题:

async function stopRecording () {
  try {
    if (mediaRecorder && mediaRecorder.state !== 'inactive') {
      // 用Promise等待onstop事件触发,确保录制完全结束
      await new Promise(resolve => {
        const originalOnStop = mediaRecorder.onstop
        mediaRecorder.onstop = (e) => {
          originalOnStop?.(e)
          resolve()
        }
        mediaRecorder.stop()
      })
    }
    captureState.value = 'idle'
    cleanupMedia() // 主动调用清理
  } catch (err) {
    errorMessage.value = 'Error stopping recording: ' + (err?.message || String(err))
    cleanupMedia()
    captureState.value = 'idle'
  }
}

5. 给媒体流添加更明确的约束(可选但推荐)

在获取媒体流时,明确指定音频参数,帮助iOS WebKit正确分配资源:

mediaStream = await navigator.mediaDevices.getUserMedia({
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
    sampleRate: 44100, // 明确采样率,避免iOS设备兼容性问题
    channelCount: 1 // 单声道,减少资源占用
  },
  video: false
})

为什么这些修改能解决问题?

  • 监听visibilitychange事件,确保PWA被后台挂起时立即释放资源,不会让媒体流一直占用;
  • 优化后的cleanupMedia移除了所有事件监听,彻底切断了资源引用,让GC能正确回收;
  • 启动录制前的前置清理,避免旧的资源残留影响新的媒体流请求;
  • 等待onstop事件触发后再清理,解决了异步时序导致的资源释放不彻底问题。

我当时就是靠这些修改解决了一模一样的问题,你可以试试,应该能让你的PWA在iOS上每次都正常录制。

内容来源于stack exchange

火山引擎 最新活动