1.使用 electron + navigator.mediaDevices.getUserMedia 录制音视频
想获取视频流,首先需要获取所需要捕获视频流的 MediaSourceId
。 Electron
提供了一个获取各个“窗口”和“屏幕”视频 MediaSourceId
的通用 API
import { desktopCapturer } from 'electron';
// 获取全部窗口或屏幕的mediaSourceId
desktopCapturer.getSources({
types: ['screen', 'window'], // 设定需要捕获的是"屏幕",还是"窗口"
thumbnailSize: {
height: 300, // 窗口或屏幕的截图快照高度
width: 300 // 窗口或屏幕的截图快照宽度
},
fetchWindowIcons: true // 如果视频源是窗口且有图标,则设置该值可以捕获到的窗口图标
}).then(sources => {
sources.forEach(source => {
// 如果视频源是窗口且有图标,且fetchWindowIcons设为true,则为捕获到的窗口图标
console.log(source.appIcon);
// 显示器Id
console.log(source.display_id);
// 视频源的mediaSourceId,可通过该mediaSourceId获取视频源
console.log(source.id);
// 窗口名,通常来说与任务管理器看到的进程名一致
console.log(source.name);
// 窗口或屏幕在调用本API瞬间抓捕到的截图快照
console.log(source.thumbnail);
});
});
如果你只想获取当前窗口的 MediaSourceID
import { remote } from 'electron';
// const { remote } = require("electron");
// 获取当前窗口mediaSourceId的做法
const mediaSourceId = remote.getCurrentWindow().getMediaSourceId();
Windows音频流获取
const mediaRecorder = ref(null);
const audioRef=ref(null);//html写 <audio src="" id="download" ref="audioRef" controls></audio>
let recordedChunks = [];
const setSource = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
// audio: true, // 强行表示不录制音频,音频额外获取,但是录制的是麦克风的声音
// video: false,
/* 上面简单写法只能录制麦克风的声音,若是需要录制系统声音,需要使用以下方法 */
audio: {
mandatory: {
// 无需指定mediaSourceId就可以录音了,录得是系统音频
chromeMediaSource: "desktop",
},
},
video: {
// 如果想要录制音频,必须同样把视频的选项带上,否则会失败
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: mediaSourceId,
},
},
});
// 若是不需要视频源,手工移除点不用的视频源,即可完成音频流的获取
(stream.getVideoTracks() || []).forEach((track) =>
stream.removeTrack(track)
);
// 去除音频源 getAudioTracks() 不过一般录制视频的时候,基本上都会保留音频源
// (stream.getAudioTracks() || []).forEach((track) =>
// stream.removeTrack(track)
// );
var options = {
audioBitsPerSecond: 128000, //音频比特率为 128kbps
videoBitsPerSecond: 2500000, //视频比特率为 2.5Mbps
mimeType:"audio/webm; codecs=pcm"
};
console.log(stream, "stream");
mediaRecorder.value = new MediaRecorder(stream,options);// options 可不携带
// 更新流
mediaRecorder.value.addEventListener("dataavailable", function (e) {
if (e.data.size > 0) {
recordedChunks.push(e.data);
}
});
mediaRecorder.value.addEventListener("pause", (e) => {
console.log("暂停");
});
mediaRecorder.value.addEventListener("resume", (e) => {
console.log("继续");
});
mediaRecorder.value.addEventListener("stop", function () {
console.log(
"停止",
recordedChunks,
new Blob(recordedChunks,{ type: "audio/webm; codecs=pcm" }),
URL.createObjectURL(new Blob(recordedChunks,{ type: "audio/webm; codecs=pcm" }))
);
// 写入 audio里
audioRef.value.src = URL.createObjectURL(new Blob(recordedChunks));
});
mediaRecorder.value.addEventListener("start", (e) => {
console.log("开始");
});
} catch (e) {
console.log(e);
}
};
onMounted(() => {
setSource();
})
// 操作按钮
// 开始
const startButton = () => {
recordedChunks=[];
mediaRecorder.value.start(1000);//1s读取一次
};
// 停止
const stopButton = () => {
mediaRecorder.value.stop();
};
// 暂停/继续
const pauseButton = () => {
console.log(mediaRecorder.value.state);
if (mediaRecorder.value.state === "recording") {
mediaRecorder.value.pause();
} else if (mediaRecorder.value.state === "paused") {
mediaRecorder.value.resume();
}
};
2.使用 Recorder + navigator.mediaDevices.getUserMedia 录音音视频
Recorder链接:Recorder
import Recorder from "recorder-core";
import "recorder-core/src/engine/wav";
import "recorder-core/src/extensions/wavesurfer.view";
const fs = window.require("fs");
const { remote } = require("electron");
const { dialog } = remote;
const mediaRecorder = ref(null);
let rec = null;
const indexStr = ref("");
const index = ref(0);
const wave = ref();//绘制波形图
const blobData = ref();
let timer = null;
const recStart=(stream) => {
console.log("未开始录音", stream);
if (rec) {
//清理掉已有的
rec.close();
}
rec = Recorder({
type: "wav",
sampleRate: 16000,
bitRate: 16,
sourceStream: stream, //明确指定从这个流中录制音频
onProcess: function (
buffers,
powerLevel,
bufferDuration,
bufferSampleRate
) {
wave.value = Recorder.WaveSurferView({ elem: ".elem" });
wave.value.input(
buffers[buffers.length - 1],
powerLevel,
bufferSampleRate
);
},
});
rec.open(
function () {
setTimeout(() => {
rec.start();
}, 1000);
timer = setInterval(() => {
index.value += 1;
getTime(index.value);
}, 1000);
},
function (msg, isUserNotAllow) {
console.log((isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg);
}
);
}
const setSource = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
// 无需指定mediaSourceId就可以录音了,录得是系统音频
chromeMediaSource: "desktop",
},
},
video: {
// 如果想要录制音频,必须同样把视频的选项带上,否则会失败
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: mediaSourceId,
},
},
});
// 接着手工移除点不用的视频源,即可完成音频流的获取
(stream.getVideoTracks() || []).forEach((track) =>
stream.removeTrack(track)
);
recStart(stream);
console.log(stream, "stream");
} catch (e) {
console.log(e);
}
};
const startButton = () => {
indexStr.value = "00:00:00";
setSource();
};
const stopButton = () => {
clearInterval(timer);
timer = null;
rec.stop(
function (blob, duration) {
if (blob) {
blobData.value = blob;
}
console.log(blob,'blob');
},
function (msg) {
console.log("录音失败:" + msg);
}
);
};
const getTime = (index) => {
if (index < 10) {
indexStr.value = "00:00:0" + index;
}
if (index > 10 && index < 60) {
indexStr.value = "00:00:" + index;
}
if (index > 60 && index < 3600) {
let miao = index % 60 < 10 ? "0" + (index % 60) : index % 60;
let fen =
parseInt(index / 60) < 10
? "0" + parseInt(index / 60)
: parseInt(index / 60);
indexStr.value = "00:" + fen + ":" + miao;
}
};
// 写入 导出音频
const exportAudio = () => {
dialog
.showSaveDialog(null, {
title: "保存",
defaultPath: "recorder" + ".wav",
properties: ["openFile"],
filters: [{ name: "All Files", extensions: ["*"] }],
// 点击保存回调
})
.then(({ filePath }) => {
const fr = new FileReader();
fr.readAsArrayBuffer(blobData.value);
setTimeout(() => {
let buffer = fr.result;
var buf = new Buffer(buffer.byteLength);
var view = new Uint8Array(buffer);
for (var i = 0; i < buf.length; ++i) {
buf[i] = view[i];
}
fs.writeFile(filePath, buf, (err) => {
console.log(err, "err");
});
});
});
};
在 方法1 中,最后导出音频的时候,在 mediaRecorder.value.addEventListener("stop", function () {})
里 添加 blobData.value=new Blob(recordedChunks,{ type: "audio/wav" })
,并调用 exportAudio
方法,导致的音频并不是 wav
格式
原因是recordedChunks
流里的格式是 audio/webm; codecs=pcm
,在 new Blob
里时候,并没有改变 recordedChunks
里 mimeType
的格式,仅仅是给Blob
定义了一个type
。
并且mimeType
里并没有 audio/wav
这个格式配置 ,所以不仅在 options
里配置无效,还会报错。
tips:目前的 chrome 浏览器的采样率是只读状态且不能修改(修改无效), 需要在每一次
onaudioprocess
触发的时候对 PCM 数据进行相应的转化处理,来达到使用要求。 推荐一个转化采样率的方法:Recorder
中源码
——来源:浏览器的录音
目前官方支持的格式:
const types = [
"video/webm",
"audio/webm",
"video/webm;codecs=vp8",
"video/webm;codecs=daala",
"video/webm;codecs=h264",
"audio/webm;codecs=opus",
"video/mpeg"
];
使用 isTypeSupported
测试当前浏览器的支持状况
for (const type of types) {
console.log(`Is ${type} supported? ${MediaRecorder.isTypeSupported(type) ? "Maybe!" : "Nope :("}`);
}
3. 扩展
1.使用 enumerateDevices()
用于检查可用的输入设备
let itemOpiton=[]
navigator.mediaDevices.enumerateDevices().then((devices) => {
devices.forEach((device) => {
console.log(device,'device');
if (device.kind === "audioinput") {
itemOpiton.push({
textContent:device.label,
value:device.deviceId
})
}
});
});
2.使用 HTML Canvas
作为源 MediaStream
在 9 秒后停止录制
const canvas = document.querySelector("canvas");
// Optional frames per second argument.
const stream = canvas.captureStream(25);
const recordedChunks = [];
console.log(stream);
const options = { mimeType: "video/webm; codecs=vp9" };
const mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
function handleDataAvailable(event) {
console.log("data-available");
if (event.data.size > 0) {
recordedChunks.push(event.data);
console.log(recordedChunks);
download();
} else {
// …
}
}
function download() {
const blob = new Blob(recordedChunks, {
type: "video/webm",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "test.webm";
a.click();
window.URL.revokeObjectURL(url);
}
// demo: to download after 9sec
setTimeout((event) => {
console.log("stopping");
mediaRecorder.stop();
}, 9000);
参阅文档: