nertc-electron-sdk
Version:
nertc-electron-sdk
190 lines (189 loc) • 6.95 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require('events').EventEmitter;
const isEqual = require('lodash.isequal');
const YUVBuffer = require('yuv-buffer');
const YUVCanvas = require('yuv-canvas');
class Renderer {
constructor() {
this.cacheCanvasOpts = {};
this.yuv = {};
this.event = new EventEmitter();
this.ready = false;
this.contentMode = 0;
this.container = {};
this.canvas = {};
this.element = {};
this.forceMirror = undefined; // JS层镜像覆盖:undefined=跟帧数据,bool=强制覆盖
this.backgroundColor = 'black'; // 背景色
}
bind(element) {
// record element
this.element = element;
// create container
let container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
backgroundColor: this.backgroundColor || 'black',
});
this.container = container;
element.appendChild(this.container);
// create canvas
this.canvas = document.createElement('canvas');
this.container.appendChild(this.canvas);
this.yuv = YUVCanvas.attach(this.canvas, { webGL: false });
}
unbind() {
this.container && this.container.removeChild(this.canvas);
this.element && this.element.removeChild(this.container);
this.yuv = null;
this.element = null;
this.canvas = null;
this.view = null;
}
equalsElement(element) {
return this.element === element;
}
refreshCanvas() {
if (!this.container || !this.canvas)
return;
if (!this.cacheCanvasOpts || !Object.keys(this.cacheCanvasOpts).length)
return;
this.updateCanvas(Object.assign({}, this.cacheCanvasOpts, {
contentMode: this.contentMode,
clientWidth: this.container.clientWidth,
clientHeight: this.container.clientHeight,
}));
}
captureImage() {
// Not implemented for software renderer
return '';
}
updateCanvas(options = {
width: 0,
height: 0,
rotation: 0,
mirrorView: false,
contentMode: 0,
clientWidth: 0,
clientHeight: 0,
}) {
// check if display options changed
if (isEqual(this.cacheCanvasOpts, options)) {
return;
}
this.cacheCanvasOpts = Object.assign({}, options);
// check for rotation
if (options.rotation === 0 || options.rotation === 180) {
this.canvas.width = options.width;
this.canvas.height = options.height;
}
else if (options.rotation === 90 || options.rotation === 270) {
this.canvas.height = options.width;
this.canvas.width = options.height;
}
else {
throw new Error('Invalid value for rotation. Only support 0, 90, 180, 270');
}
let transformItems = [];
transformItems.push(`rotateZ(${options.rotation}deg)`);
this.canvas.style.width = "auto";
this.canvas.style.height = "auto";
this.canvas.style.zoom = '';
// 画布自然尺寸(已考虑 rotation 交换宽高)
let canvasW, canvasH;
if (options.rotation === 0 || options.rotation === 180) {
canvasW = options.width;
canvasH = options.height;
}
else {
canvasW = options.height;
canvasH = options.width;
}
const scaleX = options.clientWidth / canvasW;
const scaleY = options.clientHeight / canvasH;
if (options.contentMode === 0) {
// Fit: 等比缩放,保证视频完整显示
const s = Math.min(scaleX, scaleY);
transformItems.push(`scale(${s})`);
}
else if (options.contentMode === 1) {
// FullFill: 非等比拉伸,填满容器
transformItems.push(`scale(${scaleX}, ${scaleY})`);
}
else {
// CropFill: 等比缩放,填满容器,超出裁剪
const s = Math.max(scaleX, scaleY);
transformItems.push(`scale(${s})`);
}
// check for mirror:forceMirror 优先于帧数据 mirrorView
const effectiveMirror = this.forceMirror !== undefined ? this.forceMirror : options.mirrorView;
if (effectiveMirror) {
// this.canvas.style.transform = 'rotateY(180deg)';
transformItems.push('rotateY(180deg)');
}
if (transformItems.length > 0) {
let transform = `${transformItems.join(' ')}`;
this.canvas.style.transformOrigin = 'center center';
this.canvas.style.transform = transform;
}
}
drawFrame(imageData = { header, yUint8Array, uUint8Array, vUint8Array }) {
if (!this.ready) {
this.ready = true;
this.event.emit('ready');
}
let dv = new DataView(imageData.header);
// let format = dv.getUint8(0);
let mirror = dv.getUint8(1);
let contentWidth = dv.getUint16(2);
let contentHeight = dv.getUint16(4);
let left = dv.getUint16(6);
let top = dv.getUint16(8);
let right = dv.getUint16(10);
let bottom = dv.getUint16(12);
let rotation = dv.getUint16(14);
// let ts = dv.getUint32(16);
let width = contentWidth + left + right;
let height = contentHeight + top + bottom;
this.updateCanvas({
width, height, rotation,
mirrorView: mirror,
contentMode: this.contentMode,
clientWidth: this.container.clientWidth,
clientHeight: this.container.clientHeight,
});
let format = YUVBuffer.format({
width,
height,
chromaWidth: width / 2,
chromaHeight: height / 2
});
let y = YUVBuffer.lumaPlane(format, imageData.yUint8Array);
let u = YUVBuffer.chromaPlane(format, imageData.uUint8Array);
let v = YUVBuffer.chromaPlane(format, imageData.vUint8Array);
let frame = YUVBuffer.frame(format, y, u, v);
this.yuv.drawFrame(frame);
}
setContentMode(mode = 0) {
this.contentMode = mode;
}
// JS 层镜像覆盖(CSS transform)
setMirrorMode(enabled) {
this.forceMirror = enabled;
this.refreshCanvas();
}
// 设置视频背景色(letterbox 区域颜色)
setBackgroundColor(color) {
this.backgroundColor = color;
if (this.container && this.container.style) {
this.container.style.backgroundColor = color;
}
}
}
exports.default = Renderer;