iPhone 14 無法使用 MediaRecorder 錄製



我们的网站记录音频并为用户播放。它在许多不同的设备上运行了多年,但在iPhone 14上开始出现故障。我在https://nmp-recording-test.netlify.app/创建了一个测试应用程序,这样我就可以看到发生了什么。它在所有设备上都能完美运行,但只在iPhone 14上首次运行。它可以在其他iphone上使用,也可以在iPad和macbook上使用Safari或任何其他浏览器。

如果这是你做的第一个音频,看起来它会记录。如果我在其他地方得到一个AudioContext音频回放会为那个工作,但然后录音不会。

我能看到的唯一症状是它不工作时不调用MediaRecorder.ondataavailable,但我认为这是因为它没有记录。

下面是我在测试站点中看到的模式:

  1. 点击"新录制"。(电平指示器移动,触发可用数据回调)
  2. 单击"listen"我知道我刚才做了什么了
  3. 点击"新录制"。(无水平移动,无数据报告)
  4. 单击"listen">

但是如果我做任何事情,比如点击节拍器打开和关闭,那么它也不会记录第一次。

"O.G.Recording"是我做记录的原始方式,使用过时的方法createMediaStreamSource()createScriptProcessor()/createJavaScriptNode()。我想也许iPhone最终摆脱了这个,所以我创建了MediaRecorder版本。

我所做的,基本上是(截断以显示重要的部分):

const chunks = []
function onSuccess(stream: MediaStream) {
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = function (e) {
chunks.push(e.data);
}
mediaRecorder.start(1000);
}
navigator.mediaDevices.getUserMedia({ audio: true }).then(onSuccess, onError);

还有人看到iPhone 14处理录音的方式有什么不同吗?

有没有人有一个关于如何调试这个的建议?

如果你有一部iPhone 14,你会试试我上面的测试程序,如果你得到同样的结果,请告诉我吗?我们只有一台iPhone 14可以测试,也许这台设备有什么奇怪的地方。

如果它工作,你应该看到一些行,如data {"len":6784}出现每秒当你正在记录。

—EDIT—

我按照Frank zeng的建议重新编写了代码,我正在记录它,但它仍然不正确。音量真的很低,看起来有一些掉线,并且在恢复AudioContext时有一个很长的暂停。

新代码似乎在我可以访问的其他设备和浏览器中完美地工作。

—EDIT 2—

有两个问题-一个是createScriptProcessor的弃用停止工作,但第二个是一个iOS错误,在版本16.2中被修复。因此,重写使用AudioWorklet是必要的,但保持记录,一旦它开始是不需要的。

我和你有同样的问题,我认为AudioContent的API。createScriptProcessor在Iphone14中无效,我使用了关于AudioWorkletNode的新API来取代它。不要关闭流,因为iPhone 14的第二次录制太慢了,录制后记得销毁数据。经过测试,我已经解决了这个问题,这是我的代码,

// get stream
window.navigator.mediaDevices.getUserMedia(options).then(async (stream) => {
// that.stream = stream
that.context = new AudioContext()
await that.context.resume()
const rate = that.context.sampleRate || 44100
that.mp3Encoder = new lamejs.Mp3Encoder(1, rate, 128)
that.mediaSource = that.context.createMediaStreamSource(stream)
// API开始逐步淘汰了,如果可用则继续用,如果不可用则采用worklet方案写入音频数据
if (that.context.createScriptProcessor && typeof that.context.createScriptProcessor === 'function') {
that.mediaProcessor = that.context.createScriptProcessor(0, 1, 1)
that.mediaProcessor.onaudioprocess = event => {
window.postMessage({ cmd: 'encode', buf: event.inputBuffer.getChannelData(0) }, '*')
that._decode(event.inputBuffer.getChannelData(0))
}
} else { // 采用新方案
that.mediaProcessor = await that.initWorklet()
}
resolve()
})
// content of audioworklet function
async initWorklet() {
try {
/*音频流数据分析节点*/
let audioWorkletNode;
/*---------------加载AudioWorkletProcessor模块并将其添加到当前的Worklet----------------------------*/
await this.context.audioWorklet.addModule('/get-voice-node.js');
/*---------------AudioWorkletNode绑定加载后的AudioWorkletProcessor---------------------------------*/
audioWorkletNode = new AudioWorkletNode(this.context, "get-voice-node");
/*-------------AudioWorkletNode和AudioWorkletProcessor通信使用MessagePort--------------------------*/
console.log('audioWorkletNode', audioWorkletNode)
const messagePort = audioWorkletNode.port;
messagePort.onmessage = (e) => {
let channelData = e.data[0];
window.postMessage({ cmd: 'encode', buf: channelData }, '*')
this._decode(channelData)
}
return audioWorkletNode;
} catch (e) {
console.log(e)
}
}
// content of get-voice-node.js, Remember to put it in the static resource directory
class GetVoiceNode extends AudioWorkletProcessor {
/*
* options由new AudioWorkletNode()时传递
* */
constructor() {
super()
}
/*
* `inputList`和outputList`都是输入或输出的数组
* 比较坑的是只有128个样本???如何设置
* */
process (inputList, outputList, parameters) {
// console.log(inputList)
if(inputList.length>0&&inputList[0].length>0){
this.port.postMessage(inputList[0]);
}
return true //回来让系统知道我们仍处于活动状态并准备处理音频。
}
}
registerProcessor('get-voice-node', GetVoiceNode)

销毁记录实例并释放内存,如果想下次使用,最好创建新的实例

this.recorder.stop()
this.audioDurationTimer && window.clearInterval(this.audioDurationTimer)
const audioBlob = this.recorder.getMp3Blob()
// Destroy the recording instance and free the memory
this.recorder = null

最新更新