@cloudroom/electron-rtcsdk
Version:
这是一款在线音视频SDK插件,您可以从README.md中获取演示demo
333 lines (313 loc) • 12.5 kB
JavaScript
const Renderer = require("./renderer");
const { v4: uuidv4 } = require('uuid');
//canvas事件坐标转视频像素坐标
const toVideoPixel = (offsetX, offsetY, canvas) => {
const { width, height, clientWidth, clientHeight } = canvas;
const screenScalc = width / height;
const canvasScalc = clientWidth / clientHeight;
if (screenScalc < canvasScalc) {
const scalc = height / clientHeight;
const cWidth = width / scalc;
const blackMargin = (clientWidth - cWidth) / 2;
if (offsetX < blackMargin || offsetX > blackMargin + cWidth) {
return [-1, -1]
}
const offsetX2 = (offsetX - blackMargin) * scalc;
const offsetY2 = offsetY * scalc;
return [offsetX2, offsetY2];
} else {
const scalc = width / clientWidth;
const cHeight = height / scalc;
const blackMargin = (clientHeight - cHeight) / 2;
if (offsetY < blackMargin || offsetY > blackMargin + cHeight) {
return [-1, -1]
}
const offsetX2 = offsetX * scalc;
const offsetY2 = (offsetY - blackMargin) * scalc;
return [offsetX2, offsetY2];
}
}
const toCKey = (key) => {
return [1, 2, 3][key];
}
class VideoUI {
constructor(context, view, {
notifyFrameReceived = function () { },
notifyFirstFrameRender = function () { },
} = {}) {
this.id = uuidv4();
this.type = 0;
this.userId = '';
this.videoID = -1;
this.context = context;
this.timeId = null;
this.lastFrame = null;
this._markerObj = {
allowMark: false,//允许标注
color: '#000000',
width: 2,//画笔宽度,默认:2
// penType: 1,//画笔类型,1:铅笔, 2:水笔。默认:1
svg: null,//颜色,格式为:#RRGGBB
};
this.notifyFrameReceived = notifyFrameReceived; //帧回调
this.notifyFirstFrameRender = notifyFirstFrameRender; //首帧渲染完成通知
let renderer;
if (context.hardwareAcceleration) {
renderer = new Renderer.GlRenderer();
} else {
renderer = new Renderer.SoftwareRenderer();
}
renderer.bind(view);
this.renderer = renderer;
}
_render(frame) {
this.timeId && cancelAnimationFrame(this.timeId);
this.timeId = requestAnimationFrame(() => {
if (!this.renderer) return;
try {
const { _format, _width, _height, _data } = frame;
//_format: 0,视频
/*
CRVSDK_VFMT_INVALID = -1, //无效格式
CRVSDK_VFMT_YUV420P = 0, //yuv420p, 3个平面数据
CRVSDK_VFMT_ARGB32, //rgb32, 1个平面数据,0xAA,0xRR,0xGG,0xBB
CRVSDK_VFMT_RGBA32, //rgb32, 1个平面数据,0xRR,0xGG,0xBB,0xAA
CRVSDK_VFMT_H264, //h264裸数据,1个平面数据
CRVSDK_VFMT_OESTEXTURE,
CRVSDK_VFMT_NV21,
CRVSDK_VFMT_NV12,
CRVSDK_VFMT_0RGB,
CRVSDK_VFMT_RGB0,
*/
if (_format === 0) { // yuve
const yLen = _data.byteLength * 2 / 3;
const uLen = _data.byteLength / 6;
let i = 0;
const y = _data.slice(i, i += yLen);
const u = _data.slice(i, i += uLen);
const v = _data.slice(i);
this.renderer.drawFrame({
yUint8Array: y,
uUint8Array: u,
vUint8Array: v,
width: _width,
height: _height,
});
if (!this.lastFrame) {
this.lastFrame = frame;
this._createMarker();
this.notifyFirstFrameRender();
}
this.notifyFrameReceived(frame);
} else {
console.error('undeveloped');
}
} catch (error) {
console.error(error)
}
});
}
setVideo({ type = 0, userId = '', videoId = -1 }) {
if (type !== this.type || this.userId !== userId || this.videoId !== videoId) {
this.type = type;
this.userId = userId;
this.videoId = videoId;
this._stopMarking();
this.context.sdk_engine.rmCustomRender(this.id);
this.lastFrame = null;
if (type === 0) {
this.context.sdk_engine.addCustomRender(this.id, type, {
_userID: userId,
_videoID: videoId,
});
} else {
this._createMarker();
this.context.sdk_engine.addCustomRender(this.id, type);
}
}
}
setConfig({ scaleType = 0 }) {
this.renderer.setScaleType(scaleType);
}
// 屏幕共享进入控制模式
ctrlMode(bool) {
const { canvas } = this.renderer;
if (bool) {
canvas.onmousemove = (e) => {
const [x, y] = toVideoPixel(e.offsetX, e.offsetY, canvas);
if (x >= 0 && y >= 0) this.context.sdk_engine.sendMouseCtrlMsg(0, 0, x, y)
}
canvas.onmousedown = (e) => {
canvas.focus();
e.preventDefault();
const cKey = toCKey(e.button);
if (!cKey) return;
const [x, y] = toVideoPixel(e.offsetX, e.offsetY, canvas);
if (x >= 0 && y >= 0) this.context.sdk_engine.sendMouseCtrlMsg(1, cKey, x, y);
};
canvas.onmouseup = (e) => {
e.preventDefault();
const cKey = toCKey(e.button);
if (!cKey) return;
const [x, y] = toVideoPixel(e.offsetX, e.offsetY, canvas);
if (x >= 0 && y >= 0) this.context.sdk_engine.sendMouseCtrlMsg(2, cKey, x, y);
}
canvas.onmousewheel = (e) => {
e.preventDefault();
const [x, y] = toVideoPixel(e.offsetX, e.offsetY, canvas);
if (x >= 0 && y >= 0) this.context.sdk_engine.sendMouseCtrlMsg(e.wheelDelta > 0 ? 1 : 2, 4, x, y);
}
canvas.onkeydown = (e) => {
e.preventDefault();
this.context.sdk_engine.sendKeyCtrlMsg(0, e.keyCode);
}
canvas.onkeyup = (e) => {
e.preventDefault();
this.context.sdk_engine.sendKeyCtrlMsg(1, e.keyCode);
}
canvas.setAttribute("tabindex", "-1");
} else if (canvas) {
canvas.onmousemove = null;
canvas.onmousedown = null;
canvas.onmouseup = null;
canvas.onmousewheel = null;
canvas.onkeydown = null;
canvas.onkeyup = null;
canvas.removeAttribute("tabindex");
}
}
destroy() {
try {
this._stopMarking();
this.timeId && cancelAnimationFrame(this.timeId);
this.context.sdk_engine.rmCustomRender(this.id);
this.renderer.unbind();
} catch (error) {
console.error(error);
}
this.timeId = null;
this.renderer = null;
this.context._removeRender(this.id);
}
setMarkOption(obj) {
Object.assign(this._markerObj, obj);
}
_stopMarking() {
if (this._markerObj.svg) {
this.renderer.container.removeChild(this._markerObj.svg);
this._markerObj.svg = null;
}
}
_createMarker() {
if (this.type !== 1 || !this.context.sdk_engine.isScreenMarkedState() || this._markerObj.svg || !this.lastFrame) return;
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.position = 'absolute'
svg.style.top = 0
svg.style.left = 0
svg.style.width = "100%"
svg.style.height = "100%"
svg.setAttribute('viewBox', `0 0 ${this.lastFrame._width} ${this.lastFrame._height}`);
this._initMarkerSvg(svg);
this._markerObj.svg = svg;
this.renderer.container.appendChild(svg);
this.context._markList.forEach((data) => this._draw(data))
}
_initMarkerSvg(svg) {
let points, path, uuid;
function getMousePosition(svg, event) {
let x = event.clientX
let y = event.clientY
let svgPoint = svg.createSVGPoint();
svgPoint.x = x;
svgPoint.y = y;
let transformedPoint = svgPoint.matrixTransform(svg.getScreenCTM().inverse());
return { x: transformedPoint.x, y: transformedPoint.y };
}
svg.onmousedown = (event) => {
if (!this._markerObj.allowMark) {
return;
}
svg.onmousemove = (event) => {
let pos = getMousePosition(svg, event);
let d = path.getAttribute('d');
d += ` L${pos.x},${pos.y}`;
path.setAttribute('d', d);
points.push((Math.round(pos.x) << 16) | (Math.round(pos.y) & 0xFFFF));
}
svg.onmouseup = svg.onmouseleave = () => {
const data = {
color: this._markerObj.color,
penWidth: this._markerObj.width,
penType: 1,
points,
markid: uuid,
userid: this.context.myUserID,
}
this.context.sdk_engine.sendScreenMarkData(JSON.stringify(data))
path = null;
points = null;
uuid = null;
svg.onmouseup = svg.onmouseleave = svg.onmousemove = null;
};
let pos = getMousePosition(svg, event);
path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('stroke', this._markerObj.color);
path.setAttribute('stroke-width', this._markerObj.width);
path.setAttribute('fill', 'none');
path.setAttribute('d', `M${pos.x},${pos.y}`);
uuid = uuidv4();
path.setAttribute('markid', uuid);
svg.appendChild(path);
points = [(Math.round(pos.x) << 16) | (Math.round(pos.y) & 0xFFFF)];
}
}
_draw(data) {
let pathData = '';
for (let i = 0; i < data.points.length; i++) {
const [x, y] = data.points[i];
if (i === 0) {
pathData += `M${x},${y} `;
} else {
pathData += `L${x},${y} `;
}
}
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', pathData.trim());
path.setAttribute('stroke', data.color);
path.setAttribute('stroke-width', data.penWidth);
path.setAttribute('fill', 'none');
path.setAttribute('markid', data.markid);
this._markerObj.svg.appendChild(path);
}
_onScreenMarkData(data) {
if (this._markerObj.svg) {
const mark = this._markerObj.svg.querySelector(`[markid="${data.markid}"]`)
if (mark) {
this._markerObj.svg.removeChild(mark);
}
this._draw(data);
}
}
_onScreenMarkStarted() {
this._createMarker();
}
_onScreenMarkStopped() {
this._stopMarking()
}
_onScreenMarkDel(delIdList, oprUserID) {
if (this._markerObj.svg) {
delIdList.forEach((item) => {
let mark = this._markerObj.svg.querySelector(`[markid="${item}"]`);
if (mark) {
this._markerObj.svg.removeChild(mark);
}
})
}
}
_onScreenMarkClear() {
if (this._markerObj.svg) {
this._markerObj.svg.innerHTML = '';
}
}
}
module.exports = VideoUI;