UNPKG

@cloudroom/electron-rtcsdk

Version:

这是一款在线音视频SDK插件,您可以从README.md中获取演示demo

333 lines (313 loc) 12.5 kB
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;