Request/add 调用 navigator.mediaDevices.getUserMedia() 或删除视频轨道后的网络摄像头
Request/add webcam after calling navigator.mediaDevices.getUserMedia() or removing video track
我创建了一个基本的 MediaStream,它在我的 react-app 中获取视频和音频轨道,如下所示:
const getLocalStream: MediaStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
setLocalStream(getLocalStream);
const handleShowCamClick = async () => {
if (!callContext.localStream) return;
callContext.localStream.getVideoTracks().forEach((track: MediaStreamTrack) => track.enabled = true);
callContext.setShowCam(true);
};
const handleHideCamClick = () => {
if (!callContext.localStream) return;
// callContext.localStream.getVideoTracks().forEach((track: MediaStreamTrack) => track.enabled = false);
callContext.localStream.getVideoTracks().forEach((track: MediaStreamTrack) => track.stop());
callContext.setShowCam(false);
};
所以现在我希望用户能够禁用其网络摄像头。设置 track.enabled = false
将导致网络摄像头仍被网络应用程序使用,但将视频变为黑色,这不是我想要的行为。
相反,我希望网络应用程序不再使用网络摄像头。
我有一个网络摄像头,每次都会发出蓝光以表明摄像头正在录制。使用 track.enabled = false
我的网络摄像头显示它在技术上仍在记录。
如果我删除视频,track.stop()
将导致我想要的行为。网络摄像头不再使用,但是我如何将网络摄像头的视频轨道添加回 localStream
?
track.stop()
从 localStream
中删除轨道并从 MediaStream 中释放网络摄像头,但由于视频轨道不再存在,我如何请求网络摄像头的新视频轨道并附加它到 localStream
而不重新初始化 MediaStream?
以下解决方案使用 Vanilla Javascript 因为这是一个我相信将来会有很多人有兴趣解决的问题。
将此视为概念验证,可以适用于任何 Javascript 框架以支持 WebRTC 任务。
最后说明 - 这个例子只处理媒体流的获取、显示和合并。所有其他 WebRTC 内容,例如通过 RTCPeerConnection 发送流等,都必须使用此处创建的流来执行 - replacing/updating 这些超出了本示例的范围。
核心理念:
通过 getUserMedia() 获取流
将流分配给 HTMLMediaElement
使用 getVideoTracks() 仅停止视频轨道。
再次使用 getUserMedia() 获取没有音频的新流。
使用 MediaStream 构造函数创建新流,使用 - 来自新流的视频 + 来自现有流的音频,如下所示 -
new MediaStream([...newStream.getVideoTracks(), ...existingStream.getAudioTracks()]);
根据需要使用新生成的 MediaStream(即替换为 RTCPeerConnection 等)。
let localStream = null;
let mediaWrapperDiv = document.getElementById('mediaWrapper');
let videoFeedElem = document.createElement('video');
videoFeedElem.id = 'videoFeed';
videoFeedElem.width = 640;
videoFeedElem.height = 360;
videoFeedElem.autoplay = true;
videoFeedElem.setAttribute('playsinline', true);
mediaWrapperDiv.appendChild(videoFeedElem);
let fetchStreamBtn = document.getElementById('fetchStream');
let killEverythingBtn = document.getElementById('killSwitch');
let killOnlyVideoBtn = document.getElementById('killOnlyVideo');
let reattachVideoBtn = document.getElementById('reattachVideo');
async function fetchStreamFn() {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
if (localStream) {
await attachToDOM(localStream);
}
}
async function killEverythingFn() {
localStream.getTracks().map(track => track.stop());
localStream = null;
}
async function killOnlyVideoFn() {
localStream.getVideoTracks().map(track => track.stop());
}
async function reAttachVideoFn() {
let existingStream = localStream;
let newStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
});
localStream = new MediaStream([...newStream.getVideoTracks(), ...existingStream.getAudioTracks()]);
if (localStream) {
await attachToDOM(localStream);
}
}
async function attachToDOM(stream) {
videoFeedElem.srcObject = new MediaStream(stream.getTracks());
}
fetchStreamBtn.addEventListener('click', fetchStreamFn);
killOnlyVideoBtn.addEventListener('click', killOnlyVideoFn);
reattachVideoBtn.addEventListener('click', reAttachVideoFn);
killEverythingBtn.addEventListener('click', killEverythingFn);
div#mediaWrapper {
margin: 0 auto;
text-align: center;
}
div#mediaWrapper video {
object-fit: cover;
}
div#mediaWrapper video#videoFeed {
border: 2px solid blue;
}
div#btnWrapper {
text-align: center;
margin-top: 10px;
}
button {
border-radius: 0.25rem;
color: #ffffff;
display: inline-block;
font-size: 1rem;
font-weight: 400;
line-height: 1.6;
padding: 0.375rem 0.75rem;
text-align: center;
cursor: pointer;
}
button.btn-blue {
background-color: #007bff;
border: 1px solid #007bff;
}
button.btn-red {
background-color: #dc3545;
border: 1px solid #dc3545;
}
button.btn-green {
background-color: #28a745;
border: 1px solid #28a745;
}
<h3>How to check if this actually works?
<h3>
<h4>Just keep speaking in an audibly loud volume, you'll hear your own audio being played from your device's speakers.<br> You should be able to hear yourself even after you "Kill Only Video" (i.e. Webcam light goes off)
</h4>
<div id="mediaWrapper"></div>
<div id="btnWrapper">
<button id="fetchStream" class="btn-blue" type="button" title="Fetch Stream (Allow Access)">Fetch Stream</button>
<button id="killOnlyVideo" class="btn-red" type="button" title="Kill Only Video">Kill Only Video</button>
<button id="reattachVideo" class="btn-green" type="button" title="Re-attach Video">Re-attach Video</button>
<button id="killSwitch" class="btn-red" type="button" title="Kill Everything">Kill Everything</button>
</div>
我创建了一个基本的 MediaStream,它在我的 react-app 中获取视频和音频轨道,如下所示:
const getLocalStream: MediaStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
setLocalStream(getLocalStream);
const handleShowCamClick = async () => {
if (!callContext.localStream) return;
callContext.localStream.getVideoTracks().forEach((track: MediaStreamTrack) => track.enabled = true);
callContext.setShowCam(true);
};
const handleHideCamClick = () => {
if (!callContext.localStream) return;
// callContext.localStream.getVideoTracks().forEach((track: MediaStreamTrack) => track.enabled = false);
callContext.localStream.getVideoTracks().forEach((track: MediaStreamTrack) => track.stop());
callContext.setShowCam(false);
};
所以现在我希望用户能够禁用其网络摄像头。设置 track.enabled = false
将导致网络摄像头仍被网络应用程序使用,但将视频变为黑色,这不是我想要的行为。
相反,我希望网络应用程序不再使用网络摄像头。
我有一个网络摄像头,每次都会发出蓝光以表明摄像头正在录制。使用 track.enabled = false
我的网络摄像头显示它在技术上仍在记录。
如果我删除视频,track.stop()
将导致我想要的行为。网络摄像头不再使用,但是我如何将网络摄像头的视频轨道添加回 localStream
?
track.stop()
从 localStream
中删除轨道并从 MediaStream 中释放网络摄像头,但由于视频轨道不再存在,我如何请求网络摄像头的新视频轨道并附加它到 localStream
而不重新初始化 MediaStream?
以下解决方案使用 Vanilla Javascript 因为这是一个我相信将来会有很多人有兴趣解决的问题。
将此视为概念验证,可以适用于任何 Javascript 框架以支持 WebRTC 任务。
最后说明 - 这个例子只处理媒体流的获取、显示和合并。所有其他 WebRTC 内容,例如通过 RTCPeerConnection 发送流等,都必须使用此处创建的流来执行 - replacing/updating 这些超出了本示例的范围。
核心理念:
通过 getUserMedia() 获取流
将流分配给 HTMLMediaElement
使用 getVideoTracks() 仅停止视频轨道。
再次使用 getUserMedia() 获取没有音频的新流。
使用 MediaStream 构造函数创建新流,使用 - 来自新流的视频 + 来自现有流的音频,如下所示 -
new MediaStream([...newStream.getVideoTracks(), ...existingStream.getAudioTracks()]);
根据需要使用新生成的 MediaStream(即替换为 RTCPeerConnection 等)。
let localStream = null;
let mediaWrapperDiv = document.getElementById('mediaWrapper');
let videoFeedElem = document.createElement('video');
videoFeedElem.id = 'videoFeed';
videoFeedElem.width = 640;
videoFeedElem.height = 360;
videoFeedElem.autoplay = true;
videoFeedElem.setAttribute('playsinline', true);
mediaWrapperDiv.appendChild(videoFeedElem);
let fetchStreamBtn = document.getElementById('fetchStream');
let killEverythingBtn = document.getElementById('killSwitch');
let killOnlyVideoBtn = document.getElementById('killOnlyVideo');
let reattachVideoBtn = document.getElementById('reattachVideo');
async function fetchStreamFn() {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
if (localStream) {
await attachToDOM(localStream);
}
}
async function killEverythingFn() {
localStream.getTracks().map(track => track.stop());
localStream = null;
}
async function killOnlyVideoFn() {
localStream.getVideoTracks().map(track => track.stop());
}
async function reAttachVideoFn() {
let existingStream = localStream;
let newStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
});
localStream = new MediaStream([...newStream.getVideoTracks(), ...existingStream.getAudioTracks()]);
if (localStream) {
await attachToDOM(localStream);
}
}
async function attachToDOM(stream) {
videoFeedElem.srcObject = new MediaStream(stream.getTracks());
}
fetchStreamBtn.addEventListener('click', fetchStreamFn);
killOnlyVideoBtn.addEventListener('click', killOnlyVideoFn);
reattachVideoBtn.addEventListener('click', reAttachVideoFn);
killEverythingBtn.addEventListener('click', killEverythingFn);
div#mediaWrapper {
margin: 0 auto;
text-align: center;
}
div#mediaWrapper video {
object-fit: cover;
}
div#mediaWrapper video#videoFeed {
border: 2px solid blue;
}
div#btnWrapper {
text-align: center;
margin-top: 10px;
}
button {
border-radius: 0.25rem;
color: #ffffff;
display: inline-block;
font-size: 1rem;
font-weight: 400;
line-height: 1.6;
padding: 0.375rem 0.75rem;
text-align: center;
cursor: pointer;
}
button.btn-blue {
background-color: #007bff;
border: 1px solid #007bff;
}
button.btn-red {
background-color: #dc3545;
border: 1px solid #dc3545;
}
button.btn-green {
background-color: #28a745;
border: 1px solid #28a745;
}
<h3>How to check if this actually works?
<h3>
<h4>Just keep speaking in an audibly loud volume, you'll hear your own audio being played from your device's speakers.<br> You should be able to hear yourself even after you "Kill Only Video" (i.e. Webcam light goes off)
</h4>
<div id="mediaWrapper"></div>
<div id="btnWrapper">
<button id="fetchStream" class="btn-blue" type="button" title="Fetch Stream (Allow Access)">Fetch Stream</button>
<button id="killOnlyVideo" class="btn-red" type="button" title="Kill Only Video">Kill Only Video</button>
<button id="reattachVideo" class="btn-green" type="button" title="Re-attach Video">Re-attach Video</button>
<button id="killSwitch" class="btn-red" type="button" title="Kill Everything">Kill Everything</button>
</div>