使用 React 挂钩时,来自 MediaRecorder 的中间数据丢失
Intermediate data from MediaRecorder getting lost when using React hooks
我正在开发一个更高阶的组件,它将提供使用 MediaRecorder API 捕获媒体的能力。但是,当我尝试使用捕获的视频(以传递给 createObjectURL 的 Blob 形式)时,出现错误 ERR_REQUEST_RANGE_NOT_SATISFIABLE
。当我控制台记录传递给包装组件的 Blob 时,它的长度为 0。我在这个 post.
的末尾包含了我的代码
为了诊断问题,我尝试了以下测试:
handleDataAvailable
中的控制台记录 newChunks 记录正确的值(即 [Blob]
)。
- 我添加了
React.useEffect(() => console.log(chunks), [chunks]);
以查看区块是否真的在更新。这也会导致记录正确的值(即 [Blob]
)。
- 我添加了
React.useEffect(() => console.log(captured), [captured]);
以查看捕获的内容是否正在更新。这会导致记录大小为 0 的 Blob。
- 在
handleStop
中,我控制日志块和通过组合这些块创建的 blob。这将分别产生一个空数组和一个大小为 0 的 blob。
这让我相信 handleDataAvailable
正确地将每个块添加到块数组中,但是不知何故,当 handleStop
获得 运行 时数组正在被清空。
有人知道是什么导致了这种情况吗?
代码:
import React from 'react';
import { getUserMedia, getConstraints } from '../../utils/general';
const withMediaCapture = (WrappedComponent, recordingType, facingMode, deviceID) => {
const constraints = getConstraints(recordingType, facingMode, deviceID);
const type = recordingType === 'audio'
? 'audio/ogg; codecs=opus'
: 'video/webm; codecs=vp9';
return props => {
const [mediaStream, setMediaStream] = React.useState(undefined);
const [mediaRecorder, setMediaRecorder] = React.useState(undefined);
const [isRecording, setIsRecording] = React.useState(false);
const [captured, setCaptured] = React.useState(undefined);
const [chunks, setChunks] = React.useState([]);
// On mount, get the mediaStream:
const setupStream = () => {
getUserMedia(constraints)
.then(setMediaStream)
.catch(error => {/* TODO: Handle error */});
};
React.useEffect(setupStream, []);
// Once we have gotten the mediaStream, get the mediaRecorder:
const handleDataAvailable = ({ data }) => {
const newChunks = [...chunks, data];
setChunks(newChunks);
};
const handleStop = foo => {
const blob = new Blob(chunks, { type });
setCaptured(blob);
setChunks([]);
}
const getMediaRecorder = () => {
mediaStream && setMediaRecorder(Object.assign(
new MediaRecorder(mediaStream),
{
ondataavailable: handleDataAvailable,
onstop: handleStop,
},
));
}
React.useEffect(getMediaRecorder, [mediaStream]);
const toggleRecording = () => {
isRecording
? mediaRecorder.stop()
: mediaRecorder.start();
setIsRecording(!isRecording);
};
return <WrappedComponent {...{ preview: mediaStream, captured, toggleRecording, isRecording, ...props }} />;
};
};
const VideoCaptureDemo = ({ preview, captured, toggleRecording, isRecording }) => {
const previewRef = React.useRef(null);
const capturedRef = React.useRef(null);
const setupPreview = () => {
previewRef.current.srcObject = preview;
};
React.useEffect(setupPreview, [preview]);
const setupCaptured = () => {
const url = captured && window.URL.createObjectURL(captured);
capturedRef.current.src = url;
};
React.useEffect(setupCaptured, [captured]);
return (
<div>
<video ref={previewRef} autoPlay={true} muted={true} />
<video ref={capturedRef} controls />
<button onClick={toggleRecording}>
{isRecording ? 'Stop Recording' : 'Start Recording'}
</button>
</div>
);
};
export default withMediaCapture(VideoCaptureDemo, 'videoAndAudio');
handleStop
和 handleDataAvailable
都关闭了初始的空 chunks
数组。如果 handleDataAvailable
被多次调用,较早的块将丢失,并且 handleStop
将始终从空块数组创建一个 Blob。由 setChunks
引起的重新呈现将导致创建新版本的 handle
方法,但 MediaRecorder 仍将使用创建 MediaRecorder 时的版本。
您可以使用 functional updates 修复 handleDataAvailable
,但为了修复 handleStop
,我认为您最好改用 useReducer(reducer 管理两者 chunks
和 captured
) 这样你就可以分派一个动作,然后 reducer 就可以访问当前块并适当地创建 Blob。
我正在开发一个更高阶的组件,它将提供使用 MediaRecorder API 捕获媒体的能力。但是,当我尝试使用捕获的视频(以传递给 createObjectURL 的 Blob 形式)时,出现错误 ERR_REQUEST_RANGE_NOT_SATISFIABLE
。当我控制台记录传递给包装组件的 Blob 时,它的长度为 0。我在这个 post.
为了诊断问题,我尝试了以下测试:
handleDataAvailable
中的控制台记录 newChunks 记录正确的值(即[Blob]
)。- 我添加了
React.useEffect(() => console.log(chunks), [chunks]);
以查看区块是否真的在更新。这也会导致记录正确的值(即[Blob]
)。 - 我添加了
React.useEffect(() => console.log(captured), [captured]);
以查看捕获的内容是否正在更新。这会导致记录大小为 0 的 Blob。 - 在
handleStop
中,我控制日志块和通过组合这些块创建的 blob。这将分别产生一个空数组和一个大小为 0 的 blob。
这让我相信 handleDataAvailable
正确地将每个块添加到块数组中,但是不知何故,当 handleStop
获得 运行 时数组正在被清空。
有人知道是什么导致了这种情况吗?
代码:
import React from 'react';
import { getUserMedia, getConstraints } from '../../utils/general';
const withMediaCapture = (WrappedComponent, recordingType, facingMode, deviceID) => {
const constraints = getConstraints(recordingType, facingMode, deviceID);
const type = recordingType === 'audio'
? 'audio/ogg; codecs=opus'
: 'video/webm; codecs=vp9';
return props => {
const [mediaStream, setMediaStream] = React.useState(undefined);
const [mediaRecorder, setMediaRecorder] = React.useState(undefined);
const [isRecording, setIsRecording] = React.useState(false);
const [captured, setCaptured] = React.useState(undefined);
const [chunks, setChunks] = React.useState([]);
// On mount, get the mediaStream:
const setupStream = () => {
getUserMedia(constraints)
.then(setMediaStream)
.catch(error => {/* TODO: Handle error */});
};
React.useEffect(setupStream, []);
// Once we have gotten the mediaStream, get the mediaRecorder:
const handleDataAvailable = ({ data }) => {
const newChunks = [...chunks, data];
setChunks(newChunks);
};
const handleStop = foo => {
const blob = new Blob(chunks, { type });
setCaptured(blob);
setChunks([]);
}
const getMediaRecorder = () => {
mediaStream && setMediaRecorder(Object.assign(
new MediaRecorder(mediaStream),
{
ondataavailable: handleDataAvailable,
onstop: handleStop,
},
));
}
React.useEffect(getMediaRecorder, [mediaStream]);
const toggleRecording = () => {
isRecording
? mediaRecorder.stop()
: mediaRecorder.start();
setIsRecording(!isRecording);
};
return <WrappedComponent {...{ preview: mediaStream, captured, toggleRecording, isRecording, ...props }} />;
};
};
const VideoCaptureDemo = ({ preview, captured, toggleRecording, isRecording }) => {
const previewRef = React.useRef(null);
const capturedRef = React.useRef(null);
const setupPreview = () => {
previewRef.current.srcObject = preview;
};
React.useEffect(setupPreview, [preview]);
const setupCaptured = () => {
const url = captured && window.URL.createObjectURL(captured);
capturedRef.current.src = url;
};
React.useEffect(setupCaptured, [captured]);
return (
<div>
<video ref={previewRef} autoPlay={true} muted={true} />
<video ref={capturedRef} controls />
<button onClick={toggleRecording}>
{isRecording ? 'Stop Recording' : 'Start Recording'}
</button>
</div>
);
};
export default withMediaCapture(VideoCaptureDemo, 'videoAndAudio');
handleStop
和 handleDataAvailable
都关闭了初始的空 chunks
数组。如果 handleDataAvailable
被多次调用,较早的块将丢失,并且 handleStop
将始终从空块数组创建一个 Blob。由 setChunks
引起的重新呈现将导致创建新版本的 handle
方法,但 MediaRecorder 仍将使用创建 MediaRecorder 时的版本。
您可以使用 functional updates 修复 handleDataAvailable
,但为了修复 handleStop
,我认为您最好改用 useReducer(reducer 管理两者 chunks
和 captured
) 这样你就可以分派一个动作,然后 reducer 就可以访问当前块并适当地创建 Blob。