UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

472 lines (446 loc) 18.7 kB
import { fabric } from 'fabric' import {EventEmitter} from 'events' const finishIcon = "data:image/svg+xml;base64,PHN2Zw0KICAgICAgICB0PSIxNjY1OTc1ODY3NzczIg0KICAgICAgICBjbGFzcz0iaWNvbiINCiAgICAgICAgdmlld0JveD0iMCAwIDEwMjQgMTAyNCINCiAgICAgICAgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEwMzQ2IiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+DQogICAgPHBhdGggZD0iTTc3Mi4yNjY2NjcgMzU2LjI2NjY2N2wtMzU5LjY4IDM2MC45NmEzNC45ODY2NjcgMzQuOTg2NjY3IDAgMCAxLTguNTMzMzM0IDYuODI2NjY2IDM1Ljg0IDM1Ljg0IDAgMCAxLTQ5LjA2NjY2Ni0xMy42NTMzMzNsLTEwOS4yMjY2NjctMTkwLjI5MzMzM2EzNS44NCAzNS44NCAwIDAgMSA2Mi4yOTMzMzMtMzUuODRsODUuMzMzMzM0IDE0OS4zMzMzMzMgMzI3LjY4LTMyOC41MzMzMzNhMzUuODQgMzUuODQgMCAwIDEgNTEuMiA1MC43NzMzMzN6TTUxMiAwYTUxMiA1MTIgMCAxIDAgNTEyIDUxMkE1MTIgNTEyIDAgMCAwIDUxMiAweiINCiAgICAgICAgICBmaWxsPSIjZmYyYTAwIiBwLWlkPSIxMDM0NyI+DQogICAgPC9wYXRoPg0KPC9zdmc+DQo=" const rectangleDefault = [{x: 0.001, y: 0.001}, {x: 0.998, y: 0.001}, {x: 0.998, y: 0.998}, {x: 0.001, y: 0.998}] // 矩形框绘制 export class rectDraw { private cardID: string private canvas: any private fabricDom: HTMLCanvasElement private rectangle: HTMLDivElement private currentType: string private downObject: any private drawing = false private proportion: any = [] private initialPoint: any = [] private style = { color: 'rgba(255, 42, 0, .9)', border: 'rgba(255, 42, 0, .4)', strokeWidth: 2 } emitter: any // 确认图标 renderIcon(icon) { return function (ctx, left, top, styleOverride, fabricObject) { const size = this.cornerSize; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(icon, -size / 2, -size / 2, size, size); ctx.restore(); } } constructor(videoDom, cardId, dom) { const [rectangle, fabricDom] = dom this.cardID = cardId this.rectangle = rectangle this.fabricDom = fabricDom videoDom.appendChild(rectangle) this.emitter = new EventEmitter() /* const handleIcon = (eventData, transform) => { this.emitter.emit('complete') } const finishImg = document.createElement('img') finishImg.src = finishIcon fabric.Object.prototype.controls.deleteControl = new fabric.Control({ x: 0.5, y: -0.5, offsetY: -15, offsetX: 15, cursorStyle: 'pointer', mouseUpHandler: handleIcon, render: this.renderIcon(finishImg), cornerSize: 18 }) */ } on(event:any, listener:any) { this.emitter.addListener(event, listener); } off(event:any, listener:any) { this.emitter.removeListener(event, listener); } /*创建绘画/编辑绘画*/ async begin() { this.drawing = true this.rectangle.style.zIndex = '9' this.drawType('polygon') // 赋值初始值 this.initialPoint = [...this.proportion] } setDom(width, height) { if (this.canvas) { this.canvas.dispose() this.canvas = undefined } this.fabricDom.width = width this.fabricDom.height = height this.canvas = new fabric.Canvas(this.cardID, { uniformScaling: false, preserveObjectStacking: true }) this.setInfo() } setInfo() { /* const canvasMouseDown = (e) => { this.downObject = e this.downPoint = e.absolutePointer } const canvasMouseUp = (e) => { if (this.currentType === 'rect') { // 创建矩形 createRect(e.absolutePointer) } } // 创建矩形 const createRect = (pointer) => { const {downPoint, canvas, style} = this // 矩形参数计算 const top = Math.min(downPoint.y, pointer.y) const left = Math.min(downPoint.x, pointer.x) const width = Math.abs(downPoint.x - pointer.x) const height = Math.abs(downPoint.y - pointer.y) // 矩形对象 const rect = new fabric.Rect({ top, left, width, height, fill: 'transparent', strokeWidth: style.strokeWidth, stroke: style.color, scaleX: 1, scaleY: 1, strokeUniform: true, // 变形后保持宽度 objectCaching: false // 高速缓存,可在变形时减少虚化 }) // 将矩形添加到画布上 rect.setControlsVisibility({mtr: false}) // 取消旋转手柄 canvas.add(rect) this.downPoint = null if (canvas.getObjects().length > 0) { this.drawType('select') } } this.canvas.on('mouse:down', canvasMouseDown) // 鼠标在画布上按下 this.canvas.on('mouse:up', canvasMouseUp) // 鼠标在画布上松开 */ // 双击事件 const canvasDlclick = (e) => { const ePointer = e.pointer const oCoords = e.target.oCoords const polygons = e.target.points const offsetX = oCoords.p0.x - polygons[0].x const offsetY = oCoords.p0.y - polygons[0].y const offsetPointer = { x: ePointer.x - offsetX, y: ePointer.y - offsetY } // 双击位置是否与点重叠 const index = polygons.reduce((prv, v, i) => { if (v.x + 3 > offsetPointer.x && offsetPointer.x > v.x - 3 && v.y + 3 > offsetPointer.y && offsetPointer.y > v.y - 3) { prv = i } return prv }, -1) // 添加~删除~点 if (index < 0) { // 添加点 const integers = polygons.map(v => { return { x: ~~v.x, y: ~~v.y } }) const operateObj = pointIsAdd(offsetPointer, integers) if (operateObj) { polygons.splice(operateObj.index, 0, offsetPointer) this.drawType('polygon') } } else if (polygons.length > 3) { // 删除点 polygons.splice(index, 1) this.drawType('polygon') } // 判断是否有添加点 function pointIsAdd(point, segments) { const mistake = [] for (let i = 1; i < segments.length + 1; i++) { const ps = segments[i - 1] const pe = i === segments.length ? segments[0] : segments[i] const extent: number = distanceOfPointAndLine(ps.x, ps.y, pe.x, pe.y, point.x, point.y) // 距离在2.5之内 if (extent < 2.5) { mistake.push({ extent, pStart: ps, pEnd: pe, index: i }) } } if (mistake.length > 0) { return mistake.reduce((prv, cur) => { return prv.extent < cur.extent ? prv : cur }, mistake[0]) } return null } // 点到线段距离 function distanceOfPointAndLine(x1, y1, x2, y2, x, y) { const A = x - x1; const B = y - y1; const C = x2 - x1; const D = y2 - y1; const dot = A * C + B * D; const len_sq = C * C + D * D; let param = -1; if (len_sq != 0) { //线段长度不能为0 param = dot / len_sq; } let xx, yy; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { xx = x1 + param * C; yy = y1 + param * D; } const dx = x - xx; const dy = y - yy; return Math.sqrt(dx * dx + dy * dy); } } this.canvas.on('mouse:dblclick', canvasDlclick) this.rectangle.style.zIndex = '1' // 有参数时自动绘制 if (this.proportion.length > 0) { this.resetRect() } } // 绘画多边形 resetRect() { const {canvas, style, proportion} = this const {clientHeight, clientWidth} = this.fabricDom canvas.remove(canvas.getActiveObject()) const points = proportion.map(v => { return { x: v.x * clientWidth, y: v.y * clientHeight } }) const polygon = new fabric.Polygon(points, { fill: 'transparent', strokeWidth: style.strokeWidth, stroke: style.color, scaleX: 1, scaleY: 1, objectCaching: false, transparentCorners: false, cornerColor: 'blue', }); canvas.add(polygon); /*const {canvas, proportion, style} = this const {clientHeight, clientWidth} = this.fabricDom const {x, y, width, height} = proportion const rect = new fabric.Rect({ top: y * clientHeight, left: x * clientWidth, width: width * clientWidth, height: height * clientHeight, fill: 'transparent', strokeWidth: style.strokeWidth, stroke: style.color, scaleX: 1, scaleY: 1, strokeUniform: true, // 变形后保持宽度 objectCaching: false // 高速缓存,可在变形时减少虚化 }) rect.setControlsVisibility({mtr: false}) canvas.add(rect)*/ this.drawType('') } drawType(type: string) { this.currentType = type const {canvas, style} = this switch (type) { case '': canvas.selection = false canvas.skipTargetFind = true break case 'select': canvas.selection = true canvas.selectionColor = 'rgba(255, 255, 255, 0.01)' canvas.selectionBorderColor = 'rgba(255, 255, 255, 0.5)' canvas.skipTargetFind = false // 允许选中 break case 'rect': canvas.selectionColor = 'transparent' canvas.selectionBorderColor = style.border canvas.skipTargetFind = true // 禁止选中 break case 'polygon': polygonEdit() canvas.skipTargetFind = false break } // 定义一个可以定位控件的函数. // 该函数将用于绘图和交互. function polygonPositionHandler(dim, finalMatrix, fabricObject) { const x = (fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x), y = (fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y); return fabric.util.transformPoint( {x: x, y: y}, fabric.util.multiplyTransformMatrices( fabricObject.canvas.viewportTransform, fabricObject.calcTransformMatrix() ) ); } function getObjectSizeWithStroke(object) { const stroke = new fabric.Point( object.strokeUniform ? 1 / object.scaleX : 1, object.strokeUniform ? 1 / object.scaleY : 1 ).multiply(object.strokeWidth); return new fabric.Point(object.width + stroke.x, object.height + stroke.y); } // 定义一个函数,该函数将定义控件的作用 // 该函数将在每次鼠标移动时调用 // 单击并正在拖动 // 函数接收鼠标事件作为参数,当前transformat对象 // 以及画布坐标中的当前位置 // transform.target是对正在转换的当前对象的引用, function actionHandler(eventData, transform, x, y) { const polygon = transform.target, currentControl = polygon.controls[polygon.__corner], mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'), polygonBaseSize = getObjectSizeWithStroke(polygon), size = polygon._getTransformedDimensions(0, 0), finalPointPosition = { x: mouseLocalPosition.x * polygonBaseSize.x / size.x + polygon.pathOffset.x, y: mouseLocalPosition.y * polygonBaseSize.y / size.y + polygon.pathOffset.y }; polygon.points[currentControl.pointIndex] = finalPointPosition; return true; } // 定义一个函数,该函数可以在我们更改其 // width/height/top/left. function anchorWrapper(anchorIndex, fn) { return function (eventData, transform, x, y) { const fabricObject = transform.target, absolutePoint = fabric.util.transformPoint({ x: (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x), y: (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y), }, fabricObject.calcTransformMatrix()), actionPerformed = fn(eventData, transform, x, y), newDim = fabricObject._setPositionDimensions({}), polygonBaseSize = getObjectSizeWithStroke(fabricObject), newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x, newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y; fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5); return actionPerformed; } } function polygonEdit() { // 克隆自你以来你在复制什么 // 可能需要在不同时刻复制和粘贴. // 你不希望发生变化 // 稍后再反思副本. const poly = canvas.getObjects()[0]; canvas.setActiveObject(poly); const lastControl = poly.points.length - 1; poly.cornerStyle = 'circle'; poly.cornerColor = 'rgba(0,43,255,0.9)'; poly.cornerSize = 12 poly.controls = poly.points.reduce(function (acc, point, index) { acc['p' + index] = new fabric.Control({ positionHandler: polygonPositionHandler, actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler), actionName: 'modifyPolygon', pointIndex: index }); return acc; }, {}); poly.hasBorders = false; canvas.requestRenderAll(); } } finish() { this.drawing = false this.rectangle.style.zIndex = '1' const {canvas} = this this.drawType('') if (canvas.getObjects().length > 0) { canvas.discardActiveObject().renderAll() this.setRatio() return true } else { this.proportion = rectangleDefault return false } } remove() { const {canvas, downObject} = this if (downObject) { canvas.remove(downObject.target) } if (canvas.getObjects().length === 0) { this.drawType('rect') } } async setRatio() { // 计算参数 const data = await this.canvas.getObjects() if (data.length > 0) { const {clientHeight, clientWidth} = this.fabricDom const arr = Object.keys(data[0].oCoords) const oCoords = arr[0] === 'p0' ? data[0].oCoords : data[0].points this.proportion = [] for (let field in oCoords) { this.proportion.push({x: oCoords[field].x / clientWidth, y: oCoords[field].y / clientHeight}) } } } getRatio() { return this.proportion } setProportion(shape?:{x:any,y:any}[]) { let isErr = false if (shape && shape instanceof Array && shape.length > 2) { // 判断数据格式是否正确 isErr = shape.reduce((per, item) => { const isNan = parseFloat(item.x).toString() !== "NaN" && parseFloat(item.y).toString() !== "NaN" if (isNan) { if ((item.x < 0 && item.x > 1) || (item.y < 0 && item.y > 1)) { per = false } } else { per = false } return per }, true) } this.proportion = isErr ? shape : rectangleDefault if (!isErr && shape !== undefined) { console.log('错误:绘制多边形数据或格式不正确') } } // 取消绘制 async cancelRatio() { this.proportion = [...this.initialPoint] this.resetRect() } destroy() { if (this.canvas) { this.canvas.dispose() this.canvas = undefined } this.proportion = [] if(this.emitter){ this.emitter.removeAllListeners() this.emitter = null; } } }