export default class MediaStreamMixer {
constructor() {
}
/**
*
* @param opts
*/
mix(opts) {
let {audioTrack, videoTrack, stream} = opts || {};
const ms = new MediaStream();
if (stream instanceof MediaStream) {
audioTrack = stream.getAudioTracks()[0];
videoTrack = stream.getVideoTracks()[0];
}
if (audioTrack instanceof MediaStreamTrack && audioTrack.kind === 'audio') {
ms.addTrack(audioTrack);
}
//if (videoTrack instanceof MediaStreamTrack && videoTrack.kind === 'video') {
const mixed = this.mixVideo(videoTrack);
ms.addTrack(mixed);
//}
return ms;
}
mixVideo(videoTrack) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (videoTrack) {
const trackProcessor = new MediaStreamTrackProcessor(videoTrack);
const frameReader = trackProcessor.readable.getReader();
const frameParser = ({done, value: videoFrame}) => {
if (done) return
this.#draw(context, videoFrame.displayWidth, videoFrame.displayHeight, videoFrame);
videoFrame.close();
frameReader.read().then(data => frameParser(data));
}
frameReader.read().then(data => frameParser(data));
} else {
const draw = () => this.#draw(context, 1280, 720, null, '虚拟视频');
draw();
setInterval(draw.bind(this), 1E3);
}
return canvas.captureStream().getVideoTracks()[0];
}
mixVideo2(videoTrack) {
if (videoTrack) {
const canvas = new OffscreenCanvas(1, 1);//document.createElement('canvas');
const context = canvas.getContext('2d');
const trackProcessor = new MediaStreamTrackProcessor({track: videoTrack});
const trackGenerator = new MediaStreamTrackGenerator({kind: "video"});
const transformer = new TransformStream({
transform: async (videoFrame, controller) => {
this.#draw(context, videoFrame.displayWidth, videoFrame.displayHeight, videoFrame);
const newFrame = new VideoFrame(canvas, {timestamp: +new Date()});
videoFrame.close();
controller.enqueue(newFrame);
}
});
trackProcessor.readable.pipeThrough(transformer).pipeTo(trackGenerator.writable);
return trackGenerator;
} else {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
setInterval(() => this.#draw(context, 640, 480, null), 1E3);
return canvas.captureStream().getVideoTracks()[0];
}
}
#draw(ctx, width, height, img, osd = '') {
ctx.canvas.width = width;
ctx.canvas.height = height;
if (img) {
ctx.drawImage(img, 0, 0);
} else {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, width, height);
}
const fontSize = Math.max(12, height / 20 | 1);
const [textHeight, margin, padding] = [fontSize, fontSize * 0.4, fontSize * 0.2];
ctx.font = `${fontSize}px msyh`;
let text = new Date().toLocaleString();
let textWidth = ctx.measureText(text).width;
this.#drawText(ctx, text, margin, margin, textWidth, textHeight, padding);
if (osd) {
text = osd;
textWidth = ctx.measureText(text).width;
this.#drawText(ctx, text, width - (textWidth + 2 * padding + margin), height - (textHeight + 2 * padding + margin), textWidth, textHeight, padding);
}
}
#drawText(ctx, text, x, y, textWidth, textHeight, padding = 0) {
const [w, h] = [textWidth + 2 * padding, textHeight + 2 * padding];
ctx.fillStyle = '#808080c0';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, x + w / 2, y + h / 1.75);
}
}
标签:canvas,const,ctx,摄像机,videoFrame,添加,new,videoTrack,OSD
From: https://www.cnblogs.com/zh33gl/p/17814245.html